"""Email API endpoints — read, search, send, manage.

All IMAP/SMTP operations run in a thread pool via run_in_executor
because imaplib is synchronous and would block the async event loop.
"""

from __future__ import annotations

import asyncio
from functools import partial

from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel

from agntspace.infra.i18n import t


async def _run_sync(fn, *args, **kwargs):  # noqa: ANN001, ANN002, ANN003
    """Run a blocking function in the thread pool."""
    loop = asyncio.get_event_loop()
    return await loop.run_in_executor(None, partial(fn, *args, **kwargs))


def _enforce(request: Request, action: str) -> None:
    """Check the contract before any email action."""
    from agntspace.security.middleware import ActionDenied, ConfirmationRequired, enforce

    contract = request.app.state.contract
    try:
        enforce(contract, action, integration="email")
    except ActionDenied as e:
        raise HTTPException(status_code=403, detail=e.reason) from e
    except ConfirmationRequired as e:
        raise HTTPException(status_code=428, detail=e.reason) from e

router = APIRouter(prefix="/email", tags=["email"])


class EmailConnectRequest(BaseModel):
    email: str
    imap_host: str
    imap_port: int = 993
    smtp_host: str
    smtp_port: int = 587
    username: str = ""
    password: str


class EmailSendRequest(BaseModel):
    to: str
    subject: str
    body: str


class EmailMoveRequest(BaseModel):
    to_folder: str


def _registry(request: Request):  # noqa: ANN202
    """Get the EmailRegistry from app state."""
    return request.app.state.email_registry


def _get_account(request: Request, account: str):  # noqa: ANN202
    """Resolve an account, falling back to the default connected one."""
    reg = _registry(request)
    em = reg.get(account)
    if em and em.is_connected:
        return em
    if account == "default":
        em = reg.get_default()
        if em:
            return em
    return None


@router.get("/accounts")
async def list_email_accounts(request: Request) -> list[dict]:
    """List all configured email accounts."""
    return _registry(request).list_accounts()


@router.get("/status")
async def email_status(request: Request, account: str = "default") -> dict:
    """Get email connection status."""
    reg = _registry(request)
    if account == "default":
        em = reg.get_default()
    else:
        em = reg.get(account)
    if em:
        status = await em.check_status()
        return {
            "connected": status.connected,
            "email": status.metadata.get("email", ""),
            "last_sync": status.last_sync,
            "account": account,
        }
    # Legacy compat: check if any account is connected
    accounts = reg.list_accounts()
    if accounts:
        first = accounts[0]
        return {"connected": first["connected"], "email": first["email"], "account": first["id"]}
    return {"connected": False, "email": "", "account": ""}


@router.post("/connect")
async def connect_email(request: Request, body: EmailConnectRequest) -> dict:
    """Connect email with IMAP/SMTP credentials.

    The ``account`` field is optional — defaults to a slug derived from the
    email domain (e.g. ``gmail``, ``outlook``).  Connecting the same account
    again reconnects it with the new credentials.
    """
    import logging

    log = logging.getLogger(__name__)
    log.info("Email connect: host=%s email=%s", body.imap_host, body.email)

    # Derive account id from email domain if not provided
    account_id = (body.username or "").strip()
    if not account_id:
        domain = body.email.split("@")[-1].split(".")[0] if "@" in body.email else "default"
        # Make unique if domain already exists with a different email
        reg = _registry(request)
        existing = reg.get(domain)
        if existing and existing._config and existing._config.email_address != body.email:
            account_id = body.email.replace("@", "-at-").replace(".", "-")
        else:
            account_id = domain

    creds = {
        "email": body.email,
        "imap_host": body.imap_host,
        "imap_port": body.imap_port,
        "smtp_host": body.smtp_host,
        "smtp_port": body.smtp_port,
        "username": body.username or body.email,
        "password": body.password,
    }
    try:
        reg = _registry(request)
        await reg.connect(account_id, creds)
        request.app.state.credential_store.set(f"email.{account_id}", creds)
        return {"connected": True, "email": body.email, "account": account_id}
    except ConnectionError as e:
        raise HTTPException(status_code=400, detail=str(e)) from e


@router.post("/disconnect")
async def disconnect_email(request: Request, account: str = "") -> dict:
    """Disconnect an email account (or all if no account specified)."""
    reg = _registry(request)
    body: dict = {}
    try:
        body = await request.json()
    except Exception:
        pass
    account = body.get("account", account) or ""

    if account:
        await reg.disconnect(account)
        request.app.state.credential_store.delete(f"email.{account}")
        # Also try legacy key
        request.app.state.credential_store.delete("email")
    else:
        # Disconnect all
        for acct in reg.list_accounts():
            request.app.state.credential_store.delete(f"email.{acct['id']}")
        request.app.state.credential_store.delete("email")
        await reg.disconnect_all()
    return {"connected": False}


@router.get("/count")
async def inbox_count(request: Request, folder: str = "INBOX") -> dict:
    """Get total and unread email count."""
    _enforce(request, "read_inbox")
    em = _get_account(request, request.query_params.get("account", "default"))
    if not em:
        raise HTTPException(status_code=400, detail=t("api.email.not_connected"))
    return await _run_sync(em.get_inbox_count, folder=folder)


@router.post("/draft")
async def save_draft(request: Request, body: EmailSendRequest) -> dict:
    """Save an email as a draft (appears in Gmail Drafts folder)."""
    _enforce(request, "draft_content")
    em = _get_account(request, request.query_params.get("account", "default"))
    if not em:
        raise HTTPException(status_code=400, detail=t("api.email.not_connected"))
    success = await _run_sync(em.save_draft, to=body.to, subject=body.subject, body=body.body)
    if not success:
        raise HTTPException(status_code=500, detail=t("api.email.draft_save_failed"))
    return {"drafted": True, "to": body.to, "subject": body.subject}


@router.get("/inbox")
async def list_inbox(
    request: Request,
    limit: int = 20,
    unread_only: bool = False,
    folder: str = "INBOX",
) -> list[dict]:
    """List emails in a folder."""
    _enforce(request, "read_inbox")
    em = _get_account(request, request.query_params.get("account", "default"))
    if not em:
        raise HTTPException(status_code=400, detail=t("api.email.not_connected"))

    emails = await _run_sync(em.list_emails, folder=folder, limit=limit, unread_only=unread_only)
    return [
        {
            "uid": e.uid,
            "subject": e.subject,
            "sender": e.sender,
            "date": e.date,
            "snippet": e.snippet,
            "is_read": e.is_read,
        }
        for e in emails
    ]


@router.get("/message/{uid}")
async def read_email(request: Request, uid: str, folder: str = "INBOX") -> dict:
    """Read a single email."""
    _enforce(request, "read_inbox")
    em = _get_account(request, request.query_params.get("account", "default"))
    if not em:
        raise HTTPException(status_code=400, detail=t("api.email.not_connected"))

    msg = await _run_sync(em.read_email, uid, folder=folder)
    if not msg:
        raise HTTPException(status_code=404, detail=t("api.email.not_found"))

    return {
        "uid": msg.uid,
        "subject": msg.subject,
        "sender": msg.sender,
        "to": msg.to,
        "date": msg.date,
        "body": msg.body,
        "is_read": msg.is_read,
    }


@router.get("/search")
async def search_emails(
    request: Request, q: str, folder: str = "INBOX", limit: int = 20
) -> list[dict]:
    """Search emails by subject or sender."""
    _enforce(request, "read_inbox")
    em = _get_account(request, request.query_params.get("account", "default"))
    if not em:
        raise HTTPException(status_code=400, detail=t("api.email.not_connected"))

    emails = await _run_sync(em.search_emails, q, folder=folder, limit=limit)
    return [
        {
            "uid": e.uid,
            "subject": e.subject,
            "sender": e.sender,
            "date": e.date,
            "snippet": e.snippet,
        }
        for e in emails
    ]


@router.post("/send")
async def send_email(request: Request, body: EmailSendRequest) -> dict:
    """Send an email."""
    _enforce(request, "send_email")
    em = _get_account(request, request.query_params.get("account", "default"))
    if not em:
        raise HTTPException(status_code=400, detail=t("api.email.not_connected"))

    success = await _run_sync(em.send_email, to=body.to, subject=body.subject, body=body.body)
    if not success:
        raise HTTPException(status_code=500, detail=t("api.email.send_failed"))

    return {"sent": True, "to": body.to, "subject": body.subject}


@router.get("/folders")
async def list_folders(request: Request) -> list[str]:
    """List available email folders."""
    _enforce(request, "list_folders")
    em = _get_account(request, request.query_params.get("account", "default"))
    if not em:
        raise HTTPException(status_code=400, detail=t("api.email.not_connected"))
    return await _run_sync(em.list_folders)


@router.post("/message/{uid}/move")
async def move_email(request: Request, uid: str, body: EmailMoveRequest, folder: str = "INBOX") -> dict:
    """Move an email to another folder."""
    _enforce(request, "move_email")
    em = _get_account(request, request.query_params.get("account", "default"))
    if not em:
        raise HTTPException(status_code=400, detail=t("api.email.not_connected"))
    success = await _run_sync(em.move_email, uid, folder, body.to_folder)
    return {"moved": success}


@router.post("/message/{uid}/read")
async def mark_read(request: Request, uid: str, folder: str = "INBOX") -> dict:
    """Mark email as read."""
    em = _get_account(request, request.query_params.get("account", "default"))
    if not em:
        raise HTTPException(status_code=400, detail=t("api.email.not_connected"))
    await _run_sync(em.mark_read, uid, folder)
    return {"marked": "read"}


@router.post("/message/{uid}/unread")
async def mark_unread(request: Request, uid: str, folder: str = "INBOX") -> dict:
    """Mark email as unread."""
    em = _get_account(request, request.query_params.get("account", "default"))
    if not em:
        raise HTTPException(status_code=400, detail=t("api.email.not_connected"))
    await _run_sync(em.mark_unread, uid, folder)
    return {"marked": "unread"}


@router.post("/sync")
async def sync_email(request: Request) -> dict:
    """Sync email — check for new messages."""
    em = _get_account(request, request.query_params.get("account", "default"))
    if not em:
        raise HTTPException(status_code=400, detail=t("api.email.not_connected"))
    return await em.sync()
