"""
Email Tools (IMAP / SMTP)
Built-in tools for sending and reading emails via standard protocols.

Credentials stored as AgentCredential with builtin_tool slugs:
  - "email_smtp" → {host, port, username, password, use_tls, from_address}
  - "email_imap" → {host, port, username, password, use_ssl}

Uses Python stdlib only (smtplib, imaplib, email).
"""

import smtplib
import imaplib
import email as email_lib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import decode_header
from email.utils import formataddr, parseaddr
import logging
from typing import Dict, Any, List, Optional
from datetime import datetime

from ..base import (
    BaseTool, ToolMetadata, ToolParameter, ToolExecutionContext,
    ToolResult, ToolPermission
)

logger = logging.getLogger(__name__)


# ─── Helpers ──────────────────────────────────────────────

def _decode_header_value(value: str) -> str:
    """Decode RFC 2047 encoded header values."""
    if not value:
        return ""
    parts = decode_header(value)
    decoded = []
    for part, charset in parts:
        if isinstance(part, bytes):
            decoded.append(part.decode(charset or "utf-8", errors="replace"))
        else:
            decoded.append(part)
    return " ".join(decoded)


def _parse_email_message(msg) -> Dict[str, Any]:
    """Extract a clean dict from an email.message.Message object."""
    # Subject
    subject = _decode_header_value(msg.get("Subject", ""))

    # From / To / CC
    from_addr = _decode_header_value(msg.get("From", ""))
    to_addr = _decode_header_value(msg.get("To", ""))
    cc_addr = _decode_header_value(msg.get("Cc", ""))

    # Date
    date_str = msg.get("Date", "")

    # Message-ID
    message_id = msg.get("Message-ID", "")

    # Body extraction
    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:
                filename = part.get_filename()
                if filename:
                    attachments.append({
                        "filename": _decode_header_value(filename),
                        "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:
                    charset = part.get_content_charset() or "utf-8"
                    body = payload.decode(charset, errors="replace")
            elif content_type == "text/html" and not html_body:
                payload = part.get_payload(decode=True)
                if payload:
                    charset = part.get_content_charset() or "utf-8"
                    html_body = payload.decode(charset, errors="replace")
    else:
        payload = msg.get_payload(decode=True)
        if payload:
            charset = msg.get_content_charset() or "utf-8"
            body = payload.decode(charset, errors="replace")

    return {
        "message_id": message_id,
        "subject": subject,
        "from": from_addr,
        "to": to_addr,
        "cc": cc_addr,
        "date": date_str,
        "body": body[:5000] if body else (html_body[:5000] if html_body else ""),
        "has_html": bool(html_body),
        "attachments": attachments,
    }


def _resolve_email_creds(context: ToolExecutionContext, slug: str) -> dict:
    """Resolve SMTP or IMAP credentials from the agent profile."""
    # Workspace agent: credentials via vault, not Django ORM
# from agent.services.credential_resolver import CredentialResolver

    agent_profile = context.agent_profile or (
        context.metadata.get('agent_profile') if context.metadata else None
    )
    if not agent_profile:
        return {}

    return CredentialResolver.resolve_builtin(
        agent_profile=agent_profile,
        builtin_tool_slug=slug,
    )


# ─── SEND_EMAIL ───────────────────────────────────────────

class SendEmailTool(BaseTool):
    """Send emails via SMTP."""

    def get_metadata(self) -> ToolMetadata:
        return ToolMetadata(
            name="SEND_EMAIL",
            category="email",
            description=(
                "Send an email via SMTP. Supports plain text and HTML emails "
                "with CC/BCC recipients.\n\n"
                "Requires SMTP credentials configured in the agent's Credential Manager "
                "(provider: 'Email (SMTP)').\n\n"
                "Common SMTP servers:\n"
                "  • Gmail: smtp.gmail.com:465 (SSL) — requires App Password\n"
                "  • Outlook: smtp.office365.com:587 (TLS)\n"
                "  • Yahoo: smtp.mail.yahoo.com:465 (SSL)\n"
            ),
            version="1.0",
            parameters=[
                ToolParameter(name="to", type="string", required=True,
                              description="Recipient email address(es), comma-separated"),
                ToolParameter(name="subject", type="string", required=True,
                              description="Email subject line"),
                ToolParameter(name="body", type="string", required=True,
                              description="Email body content"),
                ToolParameter(name="cc", type="string", required=False,
                              description="CC recipients, comma-separated"),
                ToolParameter(name="bcc", type="string", required=False,
                              description="BCC recipients, comma-separated"),
                ToolParameter(name="html", type="bool", required=False, default=False,
                              description="If true, send body as HTML"),
                ToolParameter(name="reply_to", type="string", required=False,
                              description="Reply-To address"),
            ],
            permissions=[ToolPermission.NETWORK],
            examples=[
                'SEND_EMAIL(to="user@example.com", subject="Hello", body="Hi there!")',
                'SEND_EMAIL(to="a@x.com, b@x.com", subject="Update", body="<h1>News</h1>", html=true)',
            ],
            tags=["email", "smtp", "send", "communication"],
        )

    def execute(self, params: Dict[str, Any], context: ToolExecutionContext) -> ToolResult:
        to = params.get("to", "").strip()
        subject = params.get("subject", "").strip()
        body = params.get("body", "")
        cc = params.get("cc", "").strip()
        bcc = params.get("bcc", "").strip()
        is_html = params.get("html", False)
        reply_to = params.get("reply_to", "").strip()

        if not to:
            return ToolResult(success=False, output="", error="'to' is required")
        if not subject:
            return ToolResult(success=False, output="", error="'subject' is required")

        # Resolve SMTP credentials
        creds = _resolve_email_creds(context, "email_smtp")
        if not creds or not creds.get("host"):
            return ToolResult(
                success=False, output="",
                error=(
                    "No SMTP credentials configured for this agent. "
                    "Please add 'Email (SMTP)' credentials in the Credential Manager."
                )
            )

        host = creds["host"]
        port = int(creds.get("port", 465))
        username = creds.get("username", "")
        password = creds.get("password", "")
        use_tls = str(creds.get("use_tls", "true")).lower() in ("true", "1", "yes")
        from_address = creds.get("from_address") or username

        # Build recipients
        to_list = [addr.strip() for addr in to.split(",") if addr.strip()]
        cc_list = [addr.strip() for addr in cc.split(",") if addr.strip()] if cc else []
        bcc_list = [addr.strip() for addr in bcc.split(",") if addr.strip()] if bcc else []
        all_recipients = to_list + cc_list + bcc_list

        # Build message
        msg = MIMEMultipart("alternative") if is_html else MIMEText(body, "plain", "utf-8")

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

        msg["From"] = from_address
        msg["To"] = ", ".join(to_list)
        msg["Subject"] = subject
        if cc_list:
            msg["Cc"] = ", ".join(cc_list)
        if reply_to:
            msg["Reply-To"] = reply_to

        # Send
        try:
            if port == 465 or (not use_tls and port == 465):
                # SSL connection
                with smtplib.SMTP_SSL(host, port, timeout=30) as server:
                    server.login(username, password)
                    server.sendmail(from_address, all_recipients, msg.as_string())
            else:
                # STARTTLS connection
                with smtplib.SMTP(host, port, timeout=30) as server:
                    if use_tls:
                        server.starttls()
                    server.login(username, password)
                    server.sendmail(from_address, all_recipients, msg.as_string())

            summary = (
                f"✅ Email sent successfully\n"
                f"From: {from_address}\n"
                f"To: {', '.join(to_list)}\n"
            )
            if cc_list:
                summary += f"CC: {', '.join(cc_list)}\n"
            summary += f"Subject: {subject}\n"

            logger.info(f"[SEND_EMAIL] Sent to {to_list} via {host}:{port}")

            return ToolResult(
                success=True,
                output=summary,
                metadata={
                    "to": to_list,
                    "cc": cc_list,
                    "subject": subject,
                    "smtp_host": host,
                }
            )

        except smtplib.SMTPAuthenticationError as e:
            logger.error(f"[SEND_EMAIL] Auth failed: {e}")
            return ToolResult(
                success=False, output="",
                error=f"SMTP authentication failed. Check username/password. Error: {e}"
            )
        except smtplib.SMTPException as e:
            logger.error(f"[SEND_EMAIL] SMTP error: {e}")
            return ToolResult(success=False, output="", error=f"SMTP error: {e}")
        except Exception as e:
            logger.error(f"[SEND_EMAIL] Unexpected error: {e}", exc_info=True)
            return ToolResult(success=False, output="", error=f"Failed to send email: {e}")


# ─── READ_EMAIL ───────────────────────────────────────────

class ReadEmailTool(BaseTool):
    """Read and manage emails via IMAP."""

    def get_metadata(self) -> ToolMetadata:
        return ToolMetadata(
            name="READ_EMAIL",
            category="email",
            description=(
                "Read, search, and manage emails via IMAP. Supports listing folders, "
                "searching emails, reading full messages, and marking as read/unread.\n\n"
                "Requires IMAP credentials configured in the agent's Credential Manager "
                "(provider: 'Email (IMAP)').\n\n"
                "Common IMAP servers:\n"
                "  • Gmail: imap.gmail.com:993 (SSL) — requires App Password\n"
                "  • Outlook: outlook.office365.com:993 (SSL)\n"
                "  • Yahoo: imap.mail.yahoo.com:993 (SSL)\n"
            ),
            version="1.0",
            parameters=[
                ToolParameter(
                    name="action", type="choice", required=True,
                    description="Action to perform",
                    choices=["list_folders", "search", "read", "mark_read", "mark_unread"]
                ),
                # search params
                ToolParameter(name="folder", type="string", required=False,
                              default="INBOX", description="Mailbox folder (default: INBOX)"),
                ToolParameter(name="limit", type="int", required=False,
                              default=10, description="Max results for search (default: 10, max: 50)"),
                ToolParameter(name="from_filter", type="string", required=False,
                              description="Filter by sender address"),
                ToolParameter(name="subject_filter", type="string", required=False,
                              description="Filter by subject (substring)"),
                ToolParameter(name="since_date", type="string", required=False,
                              description="Only emails after this date (format: YYYY-MM-DD)"),
                ToolParameter(name="unseen_only", type="bool", required=False,
                              default=False, description="Only return unread emails"),
                # read/mark params
                ToolParameter(name="message_uid", type="string", required=False,
                              description="UID of message to read or mark (required for read/mark_read/mark_unread)"),
            ],
            permissions=[ToolPermission.NETWORK],
            examples=[
                'READ_EMAIL(action="list_folders")',
                'READ_EMAIL(action="search", folder="INBOX", unseen_only=true, limit=5)',
                'READ_EMAIL(action="search", from_filter="boss@company.com", since_date="2026-02-01")',
                'READ_EMAIL(action="read", message_uid="12345")',
                'READ_EMAIL(action="mark_read", message_uid="12345")',
            ],
            tags=["email", "imap", "read", "inbox", "communication"],
        )

    def execute(self, params: Dict[str, Any], context: ToolExecutionContext) -> ToolResult:
        action = params.get("action", "").strip()
        if not action:
            return ToolResult(success=False, output="", error="'action' is required")

        # Resolve IMAP credentials
        creds = _resolve_email_creds(context, "email_imap")
        if not creds or not creds.get("host"):
            return ToolResult(
                success=False, output="",
                error=(
                    "No IMAP credentials configured for this agent. "
                    "Please add 'Email (IMAP)' credentials in the Credential Manager."
                )
            )

        host = creds["host"]
        port = int(creds.get("port", 993))
        username = creds.get("username", "")
        password = creds.get("password", "")
        use_ssl = str(creds.get("use_ssl", "true")).lower() in ("true", "1", "yes")

        try:
            # Connect
            if use_ssl:
                mail = imaplib.IMAP4_SSL(host, port, timeout=30)
            else:
                mail = imaplib.IMAP4(host, port, timeout=30)

            mail.login(username, password)

            try:
                if action == "list_folders":
                    return self._list_folders(mail)
                elif action == "search":
                    return self._search(mail, params)
                elif action == "read":
                    return self._read(mail, params)
                elif action == "mark_read":
                    return self._mark(mail, params, read=True)
                elif action == "mark_unread":
                    return self._mark(mail, params, read=False)
                else:
                    return ToolResult(
                        success=False, output="",
                        error=f"Unknown action: {action}. Use: list_folders, search, read, mark_read, mark_unread"
                    )
            finally:
                try:
                    mail.logout()
                except Exception:
                    pass

        except imaplib.IMAP4.error as e:
            logger.error(f"[READ_EMAIL] IMAP error: {e}")
            error_msg = str(e)
            if "AUTHENTICATIONFAILED" in error_msg.upper() or "LOGIN" in error_msg.upper():
                return ToolResult(
                    success=False, output="",
                    error=f"IMAP authentication failed. Check username/password. Error: {e}"
                )
            return ToolResult(success=False, output="", error=f"IMAP error: {e}")
        except Exception as e:
            logger.error(f"[READ_EMAIL] Unexpected error: {e}", exc_info=True)
            return ToolResult(success=False, output="", error=f"Failed to connect to IMAP: {e}")

    def _list_folders(self, mail: imaplib.IMAP4_SSL) -> ToolResult:
        """List all mailbox folders."""
        status, folders = mail.list()
        if status != "OK":
            return ToolResult(success=False, output="", error="Failed to list folders")

        folder_names = []
        for folder_line in folders:
            if isinstance(folder_line, bytes):
                # Parse IMAP LIST response: (\\HasNoChildren) "/" "INBOX"
                decoded = folder_line.decode("utf-8", errors="replace")
                # Extract folder name (last quoted or unquoted part)
                parts = decoded.rsplit('" ', 1)
                if len(parts) == 2:
                    name = parts[1].strip().strip('"')
                else:
                    name = decoded.rsplit(" ", 1)[-1].strip('"')
                folder_names.append(name)

        output = f"📁 Mailbox Folders ({len(folder_names)}):\n"
        for name in sorted(folder_names):
            output += f"  • {name}\n"

        return ToolResult(success=True, output=output, metadata={"folders": folder_names})

    def _search(self, mail: imaplib.IMAP4_SSL, params: dict) -> ToolResult:
        """Search for emails in a folder."""
        folder = params.get("folder", "INBOX")
        limit = min(int(params.get("limit", 10)), 50)
        from_filter = params.get("from_filter", "").strip()
        subject_filter = params.get("subject_filter", "").strip()
        since_date = params.get("since_date", "").strip()
        unseen_only = params.get("unseen_only", False)

        # Select folder
        status, _ = mail.select(folder, readonly=True)
        if status != "OK":
            return ToolResult(success=False, output="", error=f"Failed to select folder: {folder}")

        # Build IMAP search criteria
        criteria = []
        if unseen_only:
            criteria.append("UNSEEN")
        if from_filter:
            criteria.append(f'FROM "{from_filter}"')
        if subject_filter:
            criteria.append(f'SUBJECT "{subject_filter}"')
        if since_date:
            try:
                dt = datetime.strptime(since_date, "%Y-%m-%d")
                imap_date = dt.strftime("%d-%b-%Y")
                criteria.append(f"SINCE {imap_date}")
            except ValueError:
                return ToolResult(
                    success=False, output="",
                    error=f"Invalid date format: {since_date}. Use YYYY-MM-DD."
                )

        search_str = " ".join(criteria) if criteria else "ALL"

        # Search
        status, data = mail.uid("search", None, search_str)
        if status != "OK":
            return ToolResult(success=False, output="", error="Search failed")

        uids = data[0].split()
        if not uids:
            return ToolResult(
                success=True,
                output=f"No emails found in {folder} matching: {search_str}",
                metadata={"total": 0, "folder": folder}
            )

        # Take most recent N (UIDs are ascending)
        recent_uids = uids[-limit:]
        recent_uids.reverse()  # Newest first

        # Fetch headers for each
        results = []
        for uid in recent_uids:
            status, msg_data = mail.uid("fetch", uid, "(BODY.PEEK[HEADER] FLAGS)")
            if status != "OK" or not msg_data or not msg_data[0]:
                continue

            raw_header = msg_data[0][1] if isinstance(msg_data[0], tuple) else msg_data[0]
            if isinstance(raw_header, bytes):
                msg = email_lib.message_from_bytes(raw_header)
            else:
                continue

            # Check flags for read status
            flags_data = msg_data[0][0] if isinstance(msg_data[0], tuple) else b""
            is_read = b"\\Seen" in flags_data

            results.append({
                "uid": uid.decode() if isinstance(uid, bytes) else str(uid),
                "subject": _decode_header_value(msg.get("Subject", "")),
                "from": _decode_header_value(msg.get("From", "")),
                "date": msg.get("Date", ""),
                "read": is_read,
            })

        # Format output
        output = f"📧 {folder} — {len(results)} emails (of {len(uids)} total matching)\n\n"
        for i, r in enumerate(results, 1):
            status_icon = "  " if r["read"] else "🔵"
            output += (
                f"{i}. {status_icon} {r['subject']}\n"
                f"   From: {r['from']}\n"
                f"   Date: {r['date']}\n"
                f"   UID: {r['uid']}\n\n"
            )

        return ToolResult(
            success=True,
            output=output,
            metadata={
                "folder": folder,
                "total_matching": len(uids),
                "returned": len(results),
                "emails": results,
            }
        )

    def _read(self, mail: imaplib.IMAP4_SSL, params: dict) -> ToolResult:
        """Read a specific email by UID."""
        uid = params.get("message_uid", "").strip()
        folder = params.get("folder", "INBOX")

        if not uid:
            return ToolResult(success=False, output="", error="'message_uid' is required for read action")

        # Select folder
        status, _ = mail.select(folder, readonly=True)
        if status != "OK":
            return ToolResult(success=False, output="", error=f"Failed to select folder: {folder}")

        # Fetch full message
        status, msg_data = mail.uid("fetch", uid.encode(), "(RFC822)")
        if status != "OK" or not msg_data or not msg_data[0]:
            return ToolResult(success=False, output="", error=f"Message UID {uid} not found in {folder}")

        raw_email = msg_data[0][1] if isinstance(msg_data[0], tuple) else msg_data[0]
        if not isinstance(raw_email, bytes):
            return ToolResult(success=False, output="", error="Failed to parse email data")

        msg = email_lib.message_from_bytes(raw_email)
        parsed = _parse_email_message(msg)

        # Format output
        output = (
            f"📩 Email Details\n"
            f"{'─' * 50}\n"
            f"From: {parsed['from']}\n"
            f"To: {parsed['to']}\n"
        )
        if parsed['cc']:
            output += f"CC: {parsed['cc']}\n"
        output += (
            f"Date: {parsed['date']}\n"
            f"Subject: {parsed['subject']}\n"
            f"Message-ID: {parsed['message_id']}\n"
            f"{'─' * 50}\n\n"
            f"{parsed['body']}\n"
        )

        if parsed['attachments']:
            output += f"\n📎 Attachments ({len(parsed['attachments'])}):\n"
            for att in parsed['attachments']:
                size_kb = att['size'] / 1024
                output += f"  • {att['filename']} ({att['content_type']}, {size_kb:.1f} KB)\n"

        return ToolResult(success=True, output=output, metadata={"email": parsed})

    def _mark(self, mail: imaplib.IMAP4_SSL, params: dict, read: bool) -> ToolResult:
        """Mark an email as read or unread."""
        uid = params.get("message_uid", "").strip()
        folder = params.get("folder", "INBOX")

        if not uid:
            return ToolResult(
                success=False, output="",
                error="'message_uid' is required for mark_read/mark_unread"
            )

        # Select folder (writable)
        status, _ = mail.select(folder)
        if status != "OK":
            return ToolResult(success=False, output="", error=f"Failed to select folder: {folder}")

        flag_op = "+FLAGS" if read else "-FLAGS"
        status, _ = mail.uid("store", uid.encode(), flag_op, "\\Seen")
        if status != "OK":
            return ToolResult(success=False, output="", error=f"Failed to update flags for UID {uid}")

        action_word = "read" if read else "unread"
        return ToolResult(
            success=True,
            output=f"✅ Message UID {uid} marked as {action_word}",
            metadata={"uid": uid, "marked_as": action_word}
        )


# ─── Module factory ───────────────────────────────────────

def get_all_tools():
    """Return all email tool classes for registry."""
    return [SendEmailTool, ReadEmailTool]
