"""Gmail API helpers for Signalmark.

Handles the OAuth browser flow, token persistence, Gmail push notifications
(watch/webhook), history-based sent-mail syncing, and server-side pixel
injection into already-sent messages. All functions degrade gracefully when
the Google libraries are not installed or credentials are missing.
"""
from __future__ import annotations

import base64
import json
import logging
import os
from datetime import datetime, timedelta, timezone
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import getaddresses, parsedate_to_datetime

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
CREDENTIALS_PATH = os.environ.get(
    "GOOGLE_CREDENTIALS_PATH", os.path.join(BASE_DIR, "google_credentials.json")
)
TOKEN_PATH = os.environ.get(
    "GOOGLE_TOKEN_PATH", os.path.join(BASE_DIR, "gmail_token.json")
)
REDIRECT_URI = os.environ.get(
    "GOOGLE_REDIRECT_URI", "http://127.0.0.1:5055/oauth/callback"
)

SCOPES = [
    "https://www.googleapis.com/auth/gmail.readonly",
    "https://www.googleapis.com/auth/gmail.send",
]

WATCH_TOPIC = os.environ.get("SIGNALMARK_WATCH_TOPIC", "")
PUBSUB_TIMEOUT_SECONDS = int(os.environ.get("SIGNALMARK_PUBSUB_TIMEOUT", "86400"))

_lib_error: str | None = None
try:
    from google.auth.transport.requests import Request
    from google.oauth2.credentials import Credentials
    from google_auth_oauthlib.flow import Flow
    from googleapiclient.discovery import build
except Exception as exc:  # pragma: no cover - depends on install
    _lib_error = f"Google libraries not installed: {exc}"


def lib_error() -> str | None:
    return _lib_error


def is_configured() -> bool:
    return _lib_error is None and os.path.exists(CREDENTIALS_PATH)


def _load_credentials() -> Credentials | None:
    if not os.path.exists(TOKEN_PATH):
        return None
    return Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)


def _save_credentials(creds: Credentials) -> None:
    with open(TOKEN_PATH, "w", encoding="utf-8") as fh:
        fh.write(creds.to_json())


def has_token() -> bool:
    return os.path.exists(TOKEN_PATH)


def get_credentials() -> Credentials | None:
    creds = _load_credentials()
    if creds is None:
        return None
    if creds.expired and creds.refresh_token:
        creds.refresh(Request())
        _save_credentials(creds)
    return creds if creds.valid else None


def auth_url() -> str:
    flow = Flow.from_client_secrets_file(
        CREDENTIALS_PATH, scopes=SCOPES, redirect_uri=REDIRECT_URI,
        autogenerate_code_verifier=False,
    )
    url, _state = flow.authorization_url(
        access_type="offline", prompt="consent"
    )
    return url


def exchange_code(code: str) -> None:
    _log = logging.getLogger(__name__)
    _log.info("Exchanging OAuth code (len=%d)…", len(code))
    flow = Flow.from_client_secrets_file(
        CREDENTIALS_PATH, scopes=SCOPES, redirect_uri=REDIRECT_URI,
        autogenerate_code_verifier=False,
    )
    flow.fetch_token(code=code)
    _save_credentials(flow.credentials)
    _log.info("OAuth token saved to %s", TOKEN_PATH)


def disconnect() -> None:
    if os.path.exists(TOKEN_PATH):
        os.remove(TOKEN_PATH)


def get_service():
    creds = get_credentials()
    if creds is None:
        return None
    return build("gmail", "v1", credentials=creds, cache_discovery=False)


def account_email(service) -> str | None:
    prof = service.users().getProfile(userId="me").execute()
    return prof.get("emailAddress")


def fetch_sent(service, after_epoch: int, max_results: int = 250):
    """Yield normalized dicts for SENT messages newer than after_epoch (seconds)."""
    query = f"in:sent after:{after_epoch}"
    resp = (
        service.users()
        .messages()
        .list(userId="me", labelIds=["SENT"], q=query, maxResults=max_results)
        .execute()
    )
    for meta in resp.get("messages", []):
        msg = (
            service.users()
            .messages()
            .get(
                userId="me",
                id=meta["id"],
                format="metadata",
                metadataHeaders=["To", "Subject", "Date"],
            )
            .execute()
        )
        headers = {
            h["name"].lower(): h["value"]
            for h in msg.get("payload", {}).get("headers", [])
        }
        recipients = [addr for _name, addr in getaddresses([headers.get("to", "")]) if addr]
        date_raw = headers.get("date")
        try:
            sent_at = parsedate_to_datetime(date_raw) if date_raw else None
        except Exception:
            sent_at = None
        if not sent_at:
            sent_at = datetime.fromtimestamp(
                int(msg.get("internalDate", 0)) / 1000, tz=timezone.utc
            )
        if sent_at.tzinfo is None:
            sent_at = sent_at.replace(tzinfo=timezone.utc)
        yield {
            "gmail_id": msg["id"],
            "thread_id": msg.get("threadId"),
            "recipients": recipients or ["unknown@unknown"],
            "subject": headers.get("subject") or "(no subject)",
            "sent_at": sent_at,
        }


def send_tracked(
    service,
    sender: str,
    recipient: str,
    subject: str,
    html_body: str,
    text_body: str,
    pixel_url: str,
) -> dict:
    msg = MIMEMultipart("alternative")
    msg["To"] = recipient
    msg["From"] = sender
    msg["Subject"] = subject or ""

    msg.attach(MIMEText(text_body or " ", "plain", "utf-8"))

    pixel_html = (
        html_body
        + f'<img src="{pixel_url}" width="1" height="1" alt="" '
        f'style="display:none;border:0" />'
    )
    msg.attach(MIMEText(pixel_html, "html", "utf-8"))

    raw = base64.urlsafe_b64encode(msg.as_bytes()).decode("ascii")
    return service.users().messages().send(userId="me", body={"raw": raw}).execute()


# ─── New helpers for native-send + post-inject pipeline ───────────────────


def setup_watch(service, topic_name: str) -> dict:
    """Subscribe to Gmail push notifications for the authenticated user."""
    request_body = {
        "topicName": topic_name,
        "labelIds": ["INBOX", "SENT"],
        "labelFilterAction": "include",
    }
    return (
        service.users()
        .watch(userId="me", body=request_body)
        .execute()
    )


def stop_watch(service) -> dict:
    """Unsubscribe from Gmail push notifications."""
    return service.users().stop(userId="me").execute()


def fetch_history(service, start_history_id: str, history_types: list[str] | None = None):
    """Yield history records newer than start_history_id.

    Defaults to MESSAGE_ADD / LABEL_ADD changes.
    """
    if history_types is None:
        history_types = ["messageAdded", "labelAdded"]
    page_token = None
    while True:
        resp = (
            service.users()
            .history()
            .list(
                userId="me",
                startHistoryId=start_history_id,
                historyTypes=history_types,
                pageToken=page_token,
                maxResults=500,
            )
            .execute()
        )
        for record in resp.get("history", []):
            yield record
        page_token = resp.get("nextPageToken")
        if not page_token:
            break


def get_profile(service) -> dict:
    """Return the Gmail user profile (email, historyId, etc.)."""
    return service.users().getProfile(userId="me").execute()


def get_message_metadata(service, message_id: str) -> dict:
    """Return lightweight metadata for a message: headers + snippet."""
    msg = (
        service.users()
        .messages()
        .get(
            userId="me",
            id=message_id,
            format="metadata",
            metadataHeaders=["To", "Subject", "Cc", "Bcc", "Date", "From"],
        )
        .execute()
    )
    headers = {
        h["name"].lower(): h["value"]
        for h in msg.get("payload", {}).get("headers", [])
    }
    recipients = [addr for _name, addr in getaddresses([headers.get("to", "")]) if addr]
    if not recipients:
        recipients = [addr for _name, addr in getaddresses([headers.get("cc", "")]) if addr]
    if not recipients:
        recipients = [addr for _name, addr in getaddresses([headers.get("from", "")]) if addr]
    date_raw = headers.get("date")
    try:
        sent_at = parsedate_to_datetime(date_raw) if date_raw else None
    except Exception:
        sent_at = None
    if not sent_at:
        sent_at = datetime.fromtimestamp(
            int(msg.get("internalDate", 0)) / 1000, tz=timezone.utc
        )
    if sent_at.tzinfo is None:
        sent_at = sent_at.replace(tzinfo=timezone.utc)
    return {
        "gmail_id": msg["id"],
        "thread_id": msg.get("threadId"),
        "recipients": recipients or ["unknown@unknown"],
        "subject": headers.get("subject") or "(no subject)",
        "snippet": msg.get("snippet", ""),
        "sent_at": sent_at,
    }


def get_message_raw(service, message_id: str) -> str:
    """Return the base64url-encoded raw MIME of a message."""
    msg = (
        service.users()
        .messages()
        .get(userId="me", id=message_id, format="raw")
        .execute()
    )
    return msg.get("raw", "")


def inject_pixel_into_raw(raw_b64: str, pixel_url: str) -> str:
    """Decode raw MIME, append tracking pixel to HTML part, re-encode."""
    raw_bytes = base64.urlsafe_b64decode(raw_b64)
    mime_text = raw_bytes.decode("utf-8", errors="replace")

    pixel_img = f'<img src="{pixel_url}" width="1" height="1" alt="" style="display:none;border:0" />'

    if "Content-Type: text/html" in mime_text:
        mime_text = mime_text.replace(
            "</body>",
            f"{pixel_img}</body>",
        )
    else:
        boundary = None
        for line in mime_text.splitlines():
            if line.startswith("Content-Type: multipart/alternative; boundary="):
                boundary = line.split("boundary=")[1].strip().strip('"')
                break
        if boundary:
            closing = f"--{boundary}--".encode()
            html_part = (
                f"Content-Type: text/html; charset=UTF-8\r\n"
                f"\r\n"
                f"<div>{pixel_img}</div>\r\n"
                f"\r\n"
                f"--{boundary}--\r\n"
            ).encode("utf-8")
            raw_bytes = raw_bytes.rstrip(closing) + html_part
            return base64.urlsafe_b64encode(raw_bytes).decode("ascii")

    return base64.urlsafe_b64encode(mime_text.encode("utf-8")).decode("ascii")


def update_message_with_pixel(service, message_id: str, pixel_url: str) -> dict:
    """Post-inject a tracking pixel into an already-sent message."""
    raw_b64 = get_message_raw(service, message_id)
    new_raw = inject_pixel_into_raw(raw_b64, pixel_url)
    return (
        service.users()
        .messages()
        .update(userId="me", id=message_id, body={"raw": new_raw})
        .execute()
    )
