"""Email summarisation module backed by an OpenAI-compatible endpoint."""

from __future__ import annotations

import json
from urllib import error, request
from typing import Any

from email_assistant.config import get_settings


class Summarizer:
    """Generates LLM-powered summaries of email data."""

    def __init__(self) -> None:
        settings = get_settings()
        self._base_url = settings.llm_base_url
        self._model = settings.llm_model

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def summarize_emails(self, emails: list[dict[str, Any]]) -> str:
        """Produce a structured digest of multiple emails.

        Args:
            emails: List of email metadata dicts (as returned by
                ``GmailClient.search_messages``).

        Returns:
            A formatted multi-email summary string.
        """
        if not emails:
            return "No emails to summarize."

        prompt = self._build_multi_email_prompt(emails)
        return self._call_llm(prompt)

    def summarize_thread(self, messages: list[dict[str, Any]]) -> str:
        """Summarize a full email conversation thread.

        Args:
            messages: Ordered list of message dicts with ``body`` fields.

        Returns:
            A narrative summary of the thread.
        """
        if not messages:
            return "No messages in thread."

        prompt = self._build_thread_prompt(messages)
        return self._call_llm(prompt)

    def summarize_single(self, email: dict[str, Any]) -> str:
        """Summarize a single email's content.

        Args:
            email: Email dict with a ``body`` key.

        Returns:
            Brief summary string.
        """
        prompt = self._build_single_email_prompt(email)
        return self._call_llm(prompt)

    def categorize_emails(
        self, emails: list[dict[str, Any]]
    ) -> dict[str, list[dict[str, Any]]]:
        """Categorize emails into buckets using the LLM.

        Categories: recruiters, finance, personal, promotions, other.

        Args:
            emails: List of email metadata dicts.

        Returns:
            Dict mapping category name → list of emails.
        """
        if not emails:
            return {}

        email_summaries = json.dumps(
            [
                {
                    "index": i,
                    "sender": e.get("sender", ""),
                    "subject": e.get("subject", ""),
                    "snippet": e.get("snippet", ""),
                }
                for i, e in enumerate(emails)
            ],
            ensure_ascii=False,
        )

        prompt = (
            "Categorize each of the following emails into exactly one of "
            "these categories: recruiters, finance, personal, promotions, other.\n\n"
            "Return ONLY a valid JSON object mapping category names to lists "
            "of email indices (0-based). Example: "
            '{"recruiters": [0, 2], "finance": [1]}\n\n'
            f"Emails:\n{email_summaries}"
        )

        raw = self._call_llm(prompt)

        # Parse LLM JSON response
        try:
            cleaned = raw.strip()
            if cleaned.startswith("```"):
                cleaned = cleaned.split("\n", 1)[1]
                cleaned = cleaned.rsplit("```", 1)[0]
            mapping: dict[str, list[int]] = json.loads(cleaned)
        except (json.JSONDecodeError, IndexError):
            return {"other": emails}

        categorised: dict[str, list[dict[str, Any]]] = {}
        for cat, indices in mapping.items():
            categorised[cat] = [
                emails[i] for i in indices if 0 <= i < len(emails)
            ]
        return categorised

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    def _call_llm(self, user_prompt: str) -> str:
        """Send a prompt to the configured LLM and return the response text."""
        payload = {
            "model": self._model,
            "messages": [
                {
                    "role": "system",
                    "content": (
                        "You are a concise email summariser. Respond only "
                        "with the summary or structured output requested."
                    ),
                },
                {"role": "user", "content": user_prompt},
            ],
            "temperature": 0.3,
            "max_tokens": 2048,
            "stream": False,
        }
        headers = {
            "Content-Type": "application/json",
        }

        http_request = request.Request(
            url=f"{self._base_url}/chat/completions",
            data=json.dumps(payload).encode("utf-8"),
            headers=headers,
            method="POST",
        )
        try:
            with request.urlopen(http_request) as response:
                data = json.loads(response.read().decode("utf-8"))
        except error.HTTPError as exc:
            body = exc.read().decode("utf-8", errors="replace")
            raise RuntimeError(
                f"LLM request failed with HTTP {exc.code}: {body}"
            ) from exc
        except error.URLError as exc:
            raise RuntimeError(
                f"Could not reach local LLM server at {self._base_url}: {exc.reason}"
            ) from exc

        choices = data.get("choices")
        if not isinstance(choices, list) or not choices:
            raise RuntimeError(
                "LLM response did not include choices. "
                f"Server response: {json.dumps(data, ensure_ascii=False)}"
            )

        first = choices[0]
        if not isinstance(first, dict):
            raise RuntimeError(
                "LLM response choice is invalid. "
                f"Server response: {json.dumps(data, ensure_ascii=False)}"
            )

        message = first.get("message")
        if not isinstance(message, dict):
            raise RuntimeError(
                "LLM response did not include a valid message object. "
                f"Server response: {json.dumps(data, ensure_ascii=False)}"
            )

        return message.get("content", "") or ""

    @staticmethod
    def _build_multi_email_prompt(emails: list[dict[str, Any]]) -> str:
        """Build a prompt asking for a structured multi-email digest."""
        lines = [f"Summarize the following {len(emails)} emails:\n"]
        for i, e in enumerate(emails, 1):
            attachments = ""
            if e.get("has_attachments"):
                names = ", ".join(e.get("attachment_names", []))
                attachments = f"  Attachments: {names}\n"
            lines.append(
                f"{i}. From: {e.get('sender', 'Unknown')}\n"
                f"   Subject: {e.get('subject', '(no subject)')}\n"
                f"   Date: {e.get('date', '')}\n"
                f"   Snippet: {e.get('snippet', '')}\n"
                f"{attachments}"
            )
        lines.append(
            "\nProvide a numbered summary for each email. Include sender, "
            "subject, and a one-line summary. Note any attachments."
        )
        return "\n".join(lines)

    @staticmethod
    def _build_thread_prompt(messages: list[dict[str, Any]]) -> str:
        """Build a prompt for thread summarisation."""
        lines = [
            f"Summarize the following email thread "
            f"({len(messages)} messages):\n"
        ]
        for i, m in enumerate(messages, 1):
            body = m.get("body", m.get("snippet", ""))
            body = body[:1500] if len(body) > 1500 else body
            lines.append(
                f"--- Message {i} ---\n"
                f"From: {m.get('sender', 'Unknown')}\n"
                f"Date: {m.get('date', '')}\n"
                f"Body:\n{body}\n"
            )
        lines.append(
            "\nProvide a concise narrative summary of the conversation. "
            "Highlight key decisions, action items, and outcomes."
        )
        return "\n".join(lines)

    @staticmethod
    def _build_single_email_prompt(email: dict[str, Any]) -> str:
        """Build a prompt for a single-email summary."""
        body = email.get("body", email.get("snippet", ""))
        body = body[:3000] if len(body) > 3000 else body
        return (
            f"Summarize this email:\n\n"
            f"From: {email.get('sender', 'Unknown')}\n"
            f"Subject: {email.get('subject', '(no subject)')}\n"
            f"Date: {email.get('date', '')}\n"
            f"Body:\n{body}\n\n"
            "Provide a 1–3 sentence summary."
        )
