"""Email polling listener — runs inside the daemon."""

import asyncio
from pathlib import Path

import structlog

from fliiq.runtime.agent.config import AgentConfig
from fliiq.runtime.agent.loop import agent_loop
from fliiq.runtime.agent.prompt import assemble_agent_prompt
from fliiq.runtime.agent.setup import setup_agent_resources
from fliiq.runtime.channel_store import ChannelSessionStore
from fliiq.runtime.config import global_fliiq_dir
from fliiq.runtime.google_auth import resolve_gmail_creds
from fliiq.runtime.llm.providers import LLMConfig

log = structlog.get_logger()

POLL_INTERVAL = 60  # seconds between IMAP checks
BACKOFF_BASE = 2
BACKOFF_MAX = 300  # 5 min max backoff


def _extract_email_address(from_header: str) -> str:
    """Extract bare email from 'Name <email>' format."""
    if "<" in from_header and ">" in from_header:
        return from_header.split("<")[1].split(">")[0].strip().lower()
    return from_header.strip().lower()


class EmailListener:
    """Polls Gmail IMAP for new unread emails and routes through agent_loop."""

    def __init__(
        self,
        llm_config: LLMConfig,
        project_root: Path,
        fliiq_dir: Path | None = None,
    ):
        self._llm_config = llm_config
        self._project_root = project_root
        self._running = False
        self._seen_uids: set[str] = set()
        self._sessions: dict[str, dict] = {}
        self._session_store = ChannelSessionStore(
            fliiq_dir or global_fliiq_dir(), "email",
        )

    async def _get_or_create_session(self, sender_email: str) -> dict:
        """Get or create a per-sender session with agent resources."""
        if sender_email not in self._sessions:
            def _noop_ask_user(question: str) -> str:
                return "Not available — this is an email bot session."

            def _noop_ask_user_choice(
                question: str, options: list[dict], recommended: int,
            ) -> str:
                if options and recommended and 1 <= recommended <= len(options):
                    picked = options[recommended - 1]
                    return f"Auto-selected: {picked['label']}"
                return "Auto-selected first option"

            resources = await setup_agent_resources(
                mode="autonomous",
                llm_config=self._llm_config,
                project_root=self._project_root,
                ask_user_handler=_noop_ask_user,
                ask_user_choice_handler=_noop_ask_user_choice,
            )

            # Seed with recent persistent history (survives restarts, bridges job→listener)
            recovered = self._session_store.load_recent(sender_email)

            self._sessions[sender_email] = {
                "messages": recovered,
                "resources": resources,
            }

        return self._sessions[sender_email]

    async def _poll_emails(self) -> list[dict]:
        """Fetch unread emails via IMAP. Returns only new (unseen by this listener) emails."""
        from fliiq.data.skills.core.receive_emails.main import _fetch_emails

        address, auth_method, credential = await resolve_gmail_creds()

        loop = asyncio.get_running_loop()
        result = await loop.run_in_executor(
            None, _fetch_emails, address, auth_method, credential, 20, "UNSEEN",
        )

        new_emails = []
        for email_data in result.get("emails", []):
            uid = email_data["id"]
            if uid not in self._seen_uids:
                self._seen_uids.add(uid)
                new_emails.append(email_data)

        return new_emails

    async def _mark_read(self, uid: str) -> None:
        """Mark email as read after processing."""
        from fliiq.data.skills.core.mark_email_read.main import _mark_seen

        address, auth_method, credential = await resolve_gmail_creds()

        loop = asyncio.get_running_loop()
        await loop.run_in_executor(None, _mark_seen, address, auth_method, credential, uid)

    async def _handle_email(self, email_data: dict) -> None:
        """Process a single inbound email through agent_loop."""
        sender = email_data["from"]
        sender_key = _extract_email_address(sender)

        session = await self._get_or_create_session(sender_key)
        resources = session["resources"]
        messages = session["messages"]

        # Build tagged message with full threading metadata
        tagged = (
            f'<external_message source="email" sender="{sender}">\n'
            f"From: {email_data['from']}\n"
            f"To: {email_data.get('to', '')}\n"
            f"CC: {email_data.get('cc', '')}\n"
            f"Subject: {email_data['subject']}\n"
            f"Date: {email_data['date']}\n"
            f"Message-ID: {email_data.get('message_id', '')}\n"
            f"In-Reply-To: {email_data.get('in_reply_to', '')}\n"
            f"References: {email_data.get('references', '')}\n\n"
            f"{email_data['body']}\n"
            f"</external_message>"
        )
        messages.append({"role": "user", "content": tagged})

        # Persist inbound message
        body_text = email_data.get("body", "")
        self._session_store.log_message(sender_key, "user", body_text)

        # Enrich memory with prompt-relevant entities
        from fliiq.runtime.agent.setup import (
            _load_knowledge_context,
            enrich_knowledge_context,
            enrich_memory_context,
        )

        body_text = email_data.get("body", "")
        enriched_memory = enrich_memory_context(
            resources.memory_context, body_text, self._project_root,
        )
        knowledge_context = _load_knowledge_context()
        enriched_knowledge = enrich_knowledge_context(knowledge_context, body_text)

        system = assemble_agent_prompt(
            soul=resources.soul,
            user_soul=resources.user_soul,
            skills=resources.skill_info if resources.skill_info else None,
            mode="autonomous",
            memory_context=enriched_memory,
            user_profile=resources.user_profile,
            fliiq_email=resources.fliiq_email,
            knowledge_context=enriched_knowledge,
        )

        config = AgentConfig(mode="autonomous")

        try:
            result = await agent_loop(
                llm=resources.llm,
                messages=messages,
                tools=resources.tools,
                config=config,
                system=system,
            )
            session["messages"] = result.messages

            # Post-conversation memory extraction (non-fatal)
            try:
                if getattr(result, "iterations", 0) > 0:
                    from fliiq.runtime.memory.extractor import extract_and_save_memories

                    await extract_and_save_memories(
                        resources.llm, result.messages, self._project_root,
                    )
            except Exception:
                pass

        except Exception as e:
            log.error("email_agent_error", sender=sender_key, error=str(e))

    async def run(self) -> None:
        """Main loop: poll IMAP → process new emails → repeat."""
        self._running = True
        backoff = 0
        log.info("email_listener_running")

        while self._running:
            try:
                new_emails = await self._poll_emails()
                backoff = 0

                for email_data in new_emails:
                    try:
                        await self._handle_email(email_data)
                        await self._mark_read(email_data["id"])
                    except Exception as e:
                        log.error(
                            "email_handler_error",
                            uid=email_data.get("id"),
                            subject=email_data.get("subject", ""),
                            error=str(e),
                        )

            except Exception as e:
                backoff = min(BACKOFF_BASE ** (backoff + 1), BACKOFF_MAX)
                log.warning("email_poll_exception", error=str(e), backoff=backoff)
                await asyncio.sleep(backoff)
                continue

            # Wait before next poll
            await asyncio.sleep(POLL_INTERVAL)

        log.info("email_listener_stopped")

    async def stop(self) -> None:
        """Signal the run loop to exit."""
        self._running = False
