from __future__ import annotations

import base64
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
import hashlib
import re
import uuid

import httpx
from fastapi import HTTPException

from app.core.config import get_settings
from app.services.fiscal_document_service import find_existing_fiscal_document_by_hash, ingest_fiscal_document_bytes
from app.services.google_workspace_drive import upload_binary_file_to_drive
from app.services.google_workspace_session import ensure_google_workspace_configured, get_active_google_workspace_connection
from app.services.google_workspace_store import get_google_workspace_store
from app.services.menu_asset_service import normalize_asset_name, resolve_asset_kind, resolve_asset_mime_type
from app.services.tenant_store import FiscalDocumentInboxItemRecord, SessionIdentity, get_tenant_store


GMAIL_READONLY_SCOPE = "https://www.googleapis.com/auth/gmail.readonly"
GMAIL_API_BASE = "https://gmail.googleapis.com/gmail/v1/users/me"
_FISCAL_KEYWORDS = (
    "fattura",
    "invoice",
    "bolla",
    "ddt",
    "d.d.t",
    "documento di trasporto",
    "nota di credito",
)


def _drive_fiscal_folder_name() -> str:
    return f"Documenti fiscali {datetime.now(timezone.utc).year}"


def _decode_gmail_base64(value: str) -> bytes:
    padded = value + "=" * (-len(value) % 4)
    return base64.urlsafe_b64decode(padded.encode("utf-8"))


def _extract_header_map(payload: dict[str, object]) -> dict[str, str]:
    headers: dict[str, str] = {}
    for item in payload.get("headers", []) if isinstance(payload.get("headers"), list) else []:
        if not isinstance(item, dict):
            continue
        name = str(item.get("name") or "").strip().lower()
        value = str(item.get("value") or "").strip()
        if name and value and name not in headers:
            headers[name] = value
    return headers


def _parse_received_at(header_map: dict[str, str], internal_date: object) -> str | None:
    raw_date = header_map.get("date")
    if raw_date:
        try:
            parsed = parsedate_to_datetime(raw_date)
            if parsed.tzinfo is None:
                parsed = parsed.replace(tzinfo=timezone.utc)
            return parsed.astimezone(timezone.utc).isoformat()
        except (TypeError, ValueError, IndexError):
            pass

    if isinstance(internal_date, str) and internal_date.isdigit():
        return datetime.fromtimestamp(int(internal_date) / 1000, tz=timezone.utc).isoformat()
    return None


def _normalize_text(value: str | None) -> str:
    return re.sub(r"\s+", " ", (value or "").strip().casefold())


def _looks_like_fiscal_email(subject: str, snippet: str, attachments: list[dict[str, str]]) -> bool:
    searchable = " ".join([_normalize_text(subject), _normalize_text(snippet)])
    if any(keyword in searchable for keyword in _FISCAL_KEYWORDS):
        return True

    for attachment in attachments:
        candidate = _normalize_text(attachment.get("filename"))
        if any(keyword in candidate for keyword in _FISCAL_KEYWORDS):
            return True
    return False


def _compute_after_timestamp(connection_started_at: str, existing_items: list[FiscalDocumentInboxItemRecord]) -> int:
    timestamps: list[datetime] = []
    try:
        timestamps.append(datetime.fromisoformat(connection_started_at.replace("Z", "+00:00")))
    except ValueError:
        timestamps.append(datetime.now(timezone.utc))

    for item in existing_items:
        if not item.received_at:
            continue
        try:
            timestamps.append(datetime.fromisoformat(item.received_at.replace("Z", "+00:00")))
        except ValueError:
            continue

    latest = max(timestamps)
    return max(0, int(latest.timestamp()) - 60)


def _collect_attachment_parts(part: dict[str, object]) -> list[dict[str, str]]:
    attachments: list[dict[str, str]] = []
    filename = str(part.get("filename") or "").strip()
    mime_type = str(part.get("mimeType") or "application/octet-stream").strip() or "application/octet-stream"
    part_id = str(part.get("partId") or "").strip()
    body = part.get("body") if isinstance(part.get("body"), dict) else {}
    attachment_id = str(body.get("attachmentId") or "").strip() if isinstance(body, dict) else ""
    inline_data = str(body.get("data") or "").strip() if isinstance(body, dict) else ""

    if filename and (attachment_id or inline_data):
        attachments.append(
            {
                "filename": filename,
                "mime_type": mime_type,
                "attachment_id": attachment_id or f"inline:{part_id or filename}",
                "inline_data": inline_data,
            }
        )

    raw_parts = part.get("parts")
    if isinstance(raw_parts, list):
        for child in raw_parts:
            if isinstance(child, dict):
                attachments.extend(_collect_attachment_parts(child))
    return attachments


async def _gmail_get_json(
    client: httpx.AsyncClient,
    *,
    access_token: str,
    path: str,
    params: dict[str, object] | None = None,
) -> dict[str, object]:
    response = await client.get(
        f"{GMAIL_API_BASE}{path}",
        headers={"Authorization": f"Bearer {access_token}"},
        params=params,
    )
    response.raise_for_status()
    payload = response.json()
    return payload if isinstance(payload, dict) else {}


async def _download_attachment_bytes(
    client: httpx.AsyncClient,
    *,
    access_token: str,
    message_id: str,
    attachment_id: str,
    inline_data: str,
) -> bytes:
    if inline_data:
        return _decode_gmail_base64(inline_data)

    payload = await _gmail_get_json(
        client,
        access_token=access_token,
        path=f"/messages/{message_id}/attachments/{attachment_id}",
    )
    data = str(payload.get("data") or "").strip()
    if not data:
        raise HTTPException(status_code=502, detail="Allegato Gmail privo di contenuto.")
    return _decode_gmail_base64(data)


async def sync_fiscal_document_inbox(
    session: SessionIdentity,
    *,
    max_messages: int = 15,
) -> list[FiscalDocumentInboxItemRecord]:
    ensure_google_workspace_configured()
    store = get_tenant_store()
    settings = store.get_fiscal_document_settings(session.tenant_id)
    inbound_email = (settings.inbound_email or "").strip().lower()
    if not inbound_email:
        raise HTTPException(status_code=409, detail="Configura prima la mailbox del locale.")

    connection = await get_active_google_workspace_connection(session)
    granted_scopes = {item.strip() for item in connection.scope.split() if item.strip()}
    if GMAIL_READONLY_SCOPE not in granted_scopes:
        raise HTTPException(
            status_code=409,
            detail="Permesso Gmail non disponibile. Ricollega Google e concedi l'accesso in sola lettura alla mailbox.",
        )

    if connection.account_email and inbound_email != connection.account_email.strip().lower():
        raise HTTPException(
            status_code=409,
            detail="La mailbox configurata non coincide con l'account Google collegato.",
        )

    existing_items = store.list_fiscal_document_inbox_items(session.tenant_id, limit=200)
    after_timestamp = _compute_after_timestamp(connection.connected_at, existing_items)
    query = f"in:inbox has:attachment after:{after_timestamp}"
    synced_items: list[FiscalDocumentInboxItemRecord] = []
    client_timeout = get_settings().google_workspace_request_timeout_seconds

    async with httpx.AsyncClient(timeout=client_timeout) as client:
        payload = await _gmail_get_json(
            client,
            access_token=connection.access_token,
            path="/messages",
            params={"maxResults": max(1, min(max_messages, 50)), "q": query},
        )
        raw_messages = payload.get("messages", []) if isinstance(payload.get("messages"), list) else []

        for raw_message in raw_messages:
            if not isinstance(raw_message, dict):
                continue
            message_id = str(raw_message.get("id") or "").strip()
            if not message_id:
                continue

            message_payload = await _gmail_get_json(
                client,
                access_token=connection.access_token,
                path=f"/messages/{message_id}",
                params={"format": "full"},
            )
            payload_root = message_payload.get("payload") if isinstance(message_payload.get("payload"), dict) else {}
            header_map = _extract_header_map(payload_root)
            subject = header_map.get("subject") or "Documento fornitori"
            sender = header_map.get("from")
            received_at = _parse_received_at(header_map, message_payload.get("internalDate"))
            snippet = str(message_payload.get("snippet") or "")
            attachment_parts = _collect_attachment_parts(payload_root)
            if not attachment_parts or not _looks_like_fiscal_email(subject, snippet, attachment_parts):
                continue

            for attachment in attachment_parts:
                attachment_id = attachment["attachment_id"]
                existing = store.get_fiscal_document_inbox_item(
                    session.tenant_id,
                    message_id=message_id,
                    attachment_id=attachment_id,
                )
                if existing is not None:
                    synced_items.append(existing)
                    continue

                attachment_name = normalize_asset_name(attachment["filename"], fallback="documento-fiscale")
                mime_type = resolve_asset_mime_type(attachment_name, attachment["mime_type"])
                kind = resolve_asset_kind(attachment_name, mime_type)

                if kind == "other":
                    item = store.create_fiscal_document_inbox_item(
                        item_id=f"finbox_{uuid.uuid4().hex}",
                        tenant_id=session.tenant_id,
                        message_id=message_id,
                        attachment_id=attachment_id,
                        subject=subject,
                        sender=sender,
                        received_at=received_at,
                        attachment_name=attachment_name,
                        mime_type=mime_type,
                        sync_status="unsupported",
                        error_detail="Formato allegato non supportato per l'analisi fiscale automatica.",
                    )
                    synced_items.append(item)
                    continue

                file_hash: str | None = None
                try:
                    raw_bytes = await _download_attachment_bytes(
                        client,
                        access_token=connection.access_token,
                        message_id=message_id,
                        attachment_id=attachment_id,
                        inline_data=attachment["inline_data"],
                    )
                    file_hash = hashlib.sha256(raw_bytes).hexdigest()
                    existing_document = find_existing_fiscal_document_by_hash(session, file_hash=file_hash)
                    if existing_document is not None:
                        item = store.create_fiscal_document_inbox_item(
                            item_id=f"finbox_{uuid.uuid4().hex}",
                            tenant_id=session.tenant_id,
                            message_id=message_id,
                            attachment_id=attachment_id,
                            file_hash=file_hash,
                            subject=subject,
                            sender=sender,
                            received_at=received_at,
                            attachment_name=attachment_name,
                            mime_type=mime_type,
                            sync_status="imported",
                            document_id=existing_document.id,
                        )
                        synced_items.append(item)
                        continue
                    document = ingest_fiscal_document_bytes(
                        session,
                        filename=attachment_name,
                        content_type=mime_type,
                        raw_bytes=raw_bytes,
                    )
                    try:
                        await upload_binary_file_to_drive(
                            client,
                            access_token=connection.access_token,
                            filename=attachment_name,
                            mime_type=mime_type,
                            raw_bytes=raw_bytes,
                            folder_reference=_drive_fiscal_folder_name(),
                        )
                    except Exception:
                        pass
                    item = store.create_fiscal_document_inbox_item(
                        item_id=f"finbox_{uuid.uuid4().hex}",
                        tenant_id=session.tenant_id,
                        message_id=message_id,
                        attachment_id=attachment_id,
                        file_hash=file_hash,
                        subject=subject,
                        sender=sender,
                        received_at=received_at,
                        attachment_name=attachment_name,
                        mime_type=mime_type,
                        sync_status="imported",
                        document_id=document.id,
                    )
                except Exception as exc:
                    item = store.create_fiscal_document_inbox_item(
                        item_id=f"finbox_{uuid.uuid4().hex}",
                        tenant_id=session.tenant_id,
                        message_id=message_id,
                        attachment_id=attachment_id,
                        file_hash=file_hash,
                        subject=subject,
                        sender=sender,
                        received_at=received_at,
                        attachment_name=attachment_name,
                        mime_type=mime_type,
                        sync_status="error",
                        error_detail=str(exc)[:500],
                    )
                synced_items.append(item)

    return synced_items


async def sync_all_ready_fiscal_document_inboxes() -> None:
    tenant_store = get_tenant_store()
    for tenant_id in get_google_workspace_store().list_connected_tenant_ids():
        session = tenant_store.build_service_session(tenant_id)
        if session is None:
            continue
        settings = tenant_store.get_fiscal_document_settings(tenant_id)
        if not (settings.inbound_email or "").strip():
            continue
        try:
            await sync_fiscal_document_inbox(session, max_messages=10)
        except Exception:
            continue
