"""Email tool: send and receive emails via SMTP/IMAP."""

from __future__ import annotations

import asyncio
import email
import email.utils
import imaplib
import json
import smtplib
import ssl
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
from pathlib import Path
from typing import Any

from loguru import logger

from madpod_agent.agent.tools.base import Tool

_MAX_FETCH_EMAILS = 50
_MAX_BODY_CHARS = 10_000


class EmailTool(Tool):
    """Send and receive emails via SMTP and IMAP."""

    def __init__(
        self,
        workspace: Path,
        smtp_host: str = "",
        smtp_port: int = 587,
        imap_host: str = "",
        imap_port: int = 993,
        username: str = "",
        password: str = "",
    ):
        self._workspace = workspace
        self._smtp_host = smtp_host
        self._smtp_port = smtp_port
        self._imap_host = imap_host
        self._imap_port = imap_port
        self._username = username
        self._password = password

    @property
    def name(self) -> str:
        return "email"

    @property
    def description(self) -> str:
        return (
            "Send and receive emails. Actions: 'send' (compose and send an email), "
            "'fetch' (retrieve recent emails from inbox), 'search' (search emails by criteria). "
            "Requires SMTP/IMAP credentials to be configured."
        )

    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["send", "fetch", "search"],
                    "description": "Email operation to perform",
                },
                "to": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Recipient email addresses (for 'send')",
                },
                "cc": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "CC recipients (for 'send')",
                },
                "subject": {
                    "type": "string",
                    "description": "Email subject (for 'send')",
                },
                "body": {
                    "type": "string",
                    "description": "Email body text (for 'send')",
                },
                "html_body": {
                    "type": "string",
                    "description": "HTML email body (for 'send', optional)",
                },
                "attachments": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "File paths to attach (relative to workspace, for 'send')",
                },
                "folder": {
                    "type": "string",
                    "description": "IMAP folder (default: INBOX, for 'fetch'/'search')",
                },
                "max_results": {
                    "type": "integer",
                    "description": f"Max emails to return (default: 10, max: {_MAX_FETCH_EMAILS})",
                },
                "search_query": {
                    "type": "string",
                    "description": "IMAP search query for 'search' (e.g., 'FROM john SUBJECT invoice')",
                },
                "since_date": {
                    "type": "string",
                    "description": "Fetch emails since this date (DD-Mon-YYYY, e.g., '01-Jan-2024')",
                },
            },
            "required": ["action"],
        }

    async def execute(self, action: str, **kwargs: Any) -> str:
        if action == "send":
            return await self._send(**kwargs)
        elif action == "fetch":
            return await self._fetch(**kwargs)
        elif action == "search":
            return await self._search(**kwargs)
        else:
            return json.dumps({"error": f"Unknown action: {action}"})

    async def _send(
        self,
        to: list[str] | None = None,
        cc: list[str] | None = None,
        subject: str = "",
        body: str = "",
        html_body: str | None = None,
        attachments: list[str] | None = None,
        **kwargs: Any,
    ) -> str:
        if not self._smtp_host or not self._username:
            return json.dumps({"error": "SMTP not configured. Set smtp_host, username, password."})
        if not to:
            return json.dumps({"error": "At least one recipient required"})

        msg = MIMEMultipart("alternative") if html_body else MIMEMultipart()
        msg["From"] = self._username
        msg["To"] = ", ".join(to)
        if cc:
            msg["Cc"] = ", ".join(cc)
        msg["Subject"] = subject
        msg["Date"] = email.utils.formatdate(localtime=True)

        msg.attach(MIMEText(body, "plain", "utf-8"))
        if html_body:
            msg.attach(MIMEText(html_body, "html", "utf-8"))

        # Attachments
        attached_files = []
        for att_path in (attachments or []):
            full_path = self._workspace / att_path if not Path(att_path).is_absolute() else Path(att_path)
            if not full_path.exists():
                return json.dumps({"error": f"Attachment not found: {att_path}"})
            part = MIMEBase("application", "octet-stream")
            part.set_payload(full_path.read_bytes())
            encoders.encode_base64(part)
            part.add_header("Content-Disposition", f"attachment; filename={full_path.name}")
            msg.attach(part)
            attached_files.append(full_path.name)

        all_recipients = list(to) + (cc or [])

        try:
            result = await asyncio.to_thread(
                self._send_smtp, msg, all_recipients,
            )
            return result
        except Exception as exc:
            logger.error("Email send failed: {}", exc)
            return json.dumps({"error": f"Send failed: {exc}"})

    def _send_smtp(self, msg: MIMEMultipart, recipients: list[str]) -> str:
        ctx = ssl.create_default_context()
        with smtplib.SMTP(self._smtp_host, self._smtp_port) as server:
            server.starttls(context=ctx)
            server.login(self._username, self._password)
            server.sendmail(self._username, recipients, msg.as_string())

        logger.info("Email sent to {}", recipients)
        return json.dumps({
            "ok": True,
            "to": recipients,
            "subject": msg["Subject"],
        })

    async def _fetch(
        self,
        folder: str = "INBOX",
        max_results: int = 10,
        since_date: str | None = None,
        **kwargs: Any,
    ) -> str:
        if not self._imap_host or not self._username:
            return json.dumps({"error": "IMAP not configured. Set imap_host, username, password."})

        max_results = min(max_results, _MAX_FETCH_EMAILS)

        try:
            return await asyncio.to_thread(
                self._fetch_imap, folder, max_results, since_date,
            )
        except Exception as exc:
            logger.error("Email fetch failed: {}", exc)
            return json.dumps({"error": f"Fetch failed: {exc}"})

    def _fetch_imap(self, folder: str, max_results: int, since_date: str | None) -> str:
        ctx = ssl.create_default_context()
        with imaplib.IMAP4_SSL(self._imap_host, self._imap_port, ssl_context=ctx) as conn:
            conn.login(self._username, self._password)
            conn.select(folder, readonly=True)

            criteria = "ALL"
            if since_date:
                criteria = f'(SINCE "{since_date}")'

            _, msg_nums = conn.search(None, criteria)
            nums = msg_nums[0].split()
            nums = nums[-max_results:]  # Most recent

            emails = []
            for num in reversed(nums):
                _, data = conn.fetch(num, "(RFC822)")
                raw = data[0][1]
                msg = email.message_from_bytes(raw)
                emails.append(self._parse_email(msg))

        return json.dumps({
            "ok": True,
            "folder": folder,
            "count": len(emails),
            "emails": emails,
        }, default=str)

    async def _search(
        self,
        search_query: str = "",
        folder: str = "INBOX",
        max_results: int = 10,
        **kwargs: Any,
    ) -> str:
        if not self._imap_host or not self._username:
            return json.dumps({"error": "IMAP not configured"})
        if not search_query:
            return json.dumps({"error": "search_query required"})

        max_results = min(max_results, _MAX_FETCH_EMAILS)

        try:
            return await asyncio.to_thread(
                self._search_imap, search_query, folder, max_results,
            )
        except Exception as exc:
            logger.error("Email search failed: {}", exc)
            return json.dumps({"error": f"Search failed: {exc}"})

    def _search_imap(self, query: str, folder: str, max_results: int) -> str:
        ctx = ssl.create_default_context()
        with imaplib.IMAP4_SSL(self._imap_host, self._imap_port, ssl_context=ctx) as conn:
            conn.login(self._username, self._password)
            conn.select(folder, readonly=True)

            _, msg_nums = conn.search(None, query)
            nums = msg_nums[0].split()
            nums = nums[-max_results:]

            emails = []
            for num in reversed(nums):
                _, data = conn.fetch(num, "(RFC822)")
                raw = data[0][1]
                msg = email.message_from_bytes(raw)
                emails.append(self._parse_email(msg))

        return json.dumps({
            "ok": True,
            "query": query,
            "folder": folder,
            "count": len(emails),
            "emails": emails,
        }, default=str)

    def _parse_email(self, msg: email.message.Message) -> dict[str, Any]:
        """Parse an email message into a structured dict."""
        body = ""
        html_body = ""
        attachments = []

        if msg.is_multipart():
            for part in msg.walk():
                content_type = part.get_content_type()
                disposition = str(part.get("Content-Disposition", ""))
                if "attachment" in disposition:
                    attachments.append({
                        "filename": part.get_filename() or "unnamed",
                        "content_type": content_type,
                        "size": len(part.get_payload(decode=True) or b""),
                    })
                elif content_type == "text/plain" and not body:
                    payload = part.get_payload(decode=True)
                    if payload:
                        body = payload.decode("utf-8", errors="replace")
                elif content_type == "text/html" and not html_body:
                    payload = part.get_payload(decode=True)
                    if payload:
                        html_body = payload.decode("utf-8", errors="replace")
        else:
            payload = msg.get_payload(decode=True)
            if payload:
                body = payload.decode("utf-8", errors="replace")

        # Truncate long bodies
        if len(body) > _MAX_BODY_CHARS:
            body = body[:_MAX_BODY_CHARS] + "\n... [truncated]"

        return {
            "from": msg.get("From", ""),
            "to": msg.get("To", ""),
            "cc": msg.get("Cc", ""),
            "subject": msg.get("Subject", ""),
            "date": msg.get("Date", ""),
            "body": body,
            "has_html": bool(html_body),
            "attachments": attachments,
        }
