"""Read Emails node for fetching emails via IMAP."""

import asyncio
import base64
import email
import imaplib
from email.header import decode_header
from email.utils import parsedate_to_datetime
from typing import Any

from flowire_sdk import (
    BaseNode,
    BaseNodeOutput,
    NodeExecutionContext,
    NodeMetadata,
)
from pydantic import BaseModel, Field

from fw_nodes_email.credentials import IMAPCredentialSchema


def _decode_header_value(value: str | None) -> str:
    """Decode an email header value that may contain encoded words."""
    if not value:
        return ""
    decoded_parts = decode_header(value)
    parts = []
    for part, charset in decoded_parts:
        if isinstance(part, bytes):
            try:
                parts.append(part.decode(charset or "utf-8", errors="replace"))
            except LookupError:
                parts.append(part.decode("utf-8", errors="replace"))
        else:
            parts.append(part)
    return "".join(parts)


def _extract_body(msg: email.message.Message) -> tuple[str, str]:
    """Extract plain text and HTML body from an email message.

    Returns (plain_text, html_text).
    """
    plain_body = ""
    html_body = ""

    if msg.is_multipart():
        for part in msg.walk():
            content_type = part.get_content_type()
            content_disposition = str(part.get("Content-Disposition", ""))

            if "attachment" in content_disposition:
                continue

            payload = part.get_payload(decode=True)
            if payload is None:
                continue

            charset = part.get_content_charset() or "utf-8"
            try:
                text = payload.decode(charset, errors="replace")
            except LookupError:
                text = payload.decode("utf-8", errors="replace")

            if content_type == "text/plain" and not plain_body:
                plain_body = text
            elif content_type == "text/html" and not html_body:
                html_body = text
    else:
        payload = msg.get_payload(decode=True)
        if payload:
            charset = msg.get_content_charset() or "utf-8"
            try:
                text = payload.decode(charset, errors="replace")
            except LookupError:
                text = payload.decode("utf-8", errors="replace")
            if msg.get_content_type() == "text/html":
                html_body = text
            else:
                plain_body = text

    return plain_body, html_body


def _extract_attachments(msg: email.message.Message, include_content: bool = False) -> list[dict[str, Any]]:
    """Extract attachments from an email message.

    If include_content is True, includes base64-encoded file content.
    """
    attachments = []
    if not msg.is_multipart():
        return attachments

    for part in msg.walk():
        content_disposition = str(part.get("Content-Disposition", ""))
        if "attachment" not in content_disposition:
            continue

        filename = part.get_filename()
        if filename:
            filename = _decode_header_value(filename)

        raw_data = part.get_payload(decode=True) or b""

        attachment: dict[str, Any] = {
            "filename": filename or "unnamed",
            "content_type": part.get_content_type(),
            "size": len(raw_data),
        }

        if include_content:
            attachment["content_base64"] = base64.b64encode(raw_data).decode("ascii")

        attachments.append(attachment)

    return attachments


def _fetch_emails_sync(
    host: str,
    port: int,
    username: str,
    password: str,
    use_ssl: bool,
    folder: str,
    filter_criteria: str,
    max_results: int,
    mark_as_read: bool,
    include_attachment_content: bool = False,
    readonly: bool = False,
) -> list[dict[str, Any]]:
    """Synchronous IMAP fetch (run via asyncio.to_thread)."""
    if use_ssl:
        imap = imaplib.IMAP4_SSL(host, port)
    else:
        imap = imaplib.IMAP4(host, port)

    try:
        imap.login(username, password)
        status, data = imap.select(folder, readonly=readonly or not mark_as_read)
        if status != "OK":
            raise imaplib.IMAP4.error(f"Failed to select folder '{folder}': {data}")

        status, message_ids = imap.search(None, filter_criteria)
        if status != "OK" or not message_ids[0]:
            return []

        ids = message_ids[0].split()
        # Take the most recent emails (last N)
        ids = ids[-max_results:]
        # Reverse so newest is first
        ids.reverse()

        emails = []
        for msg_id in ids:
            status, msg_data = imap.fetch(msg_id, "(RFC822)")
            if status != "OK" or not msg_data[0]:
                continue

            raw_email = msg_data[0][1]
            msg = email.message_from_bytes(raw_email)

            plain_body, html_body = _extract_body(msg)
            attachments = _extract_attachments(msg, include_content=include_attachment_content)

            date_str = msg.get("Date")
            date_iso = ""
            if date_str:
                try:
                    date_iso = parsedate_to_datetime(date_str).isoformat()
                except Exception:
                    date_iso = date_str

            emails.append(
                {
                    "message_id": msg.get("Message-ID", ""),
                    "from": _decode_header_value(msg.get("From")),
                    "to": _decode_header_value(msg.get("To")),
                    "cc": _decode_header_value(msg.get("Cc")),
                    "subject": _decode_header_value(msg.get("Subject")),
                    "date": date_iso,
                    "body_plain": plain_body,
                    "body_html": html_body,
                    "attachments": attachments,
                }
            )

            if mark_as_read:
                imap.store(msg_id, "+FLAGS", "\\Seen")

        return emails
    finally:
        try:
            imap.close()
        except Exception:
            pass
        try:
            imap.logout()
        except Exception:
            pass


class ReadEmailsInput(BaseModel):
    """Input for reading emails via IMAP."""

    credential_id: str = Field(
        ...,
        description="IMAP credential with server details and authentication",
    )
    folder: str = Field(
        default="INBOX",
        description="Mailbox folder to read from (e.g., INBOX, Sent, Drafts)",
    )
    filter: str = Field(
        default="ALL",
        description='IMAP search criteria (e.g., ALL, UNSEEN, FROM "user@example.com", SINCE 01-Jan-2025)',
    )
    max_results: int = Field(
        default=10,
        description="Maximum number of emails to fetch (most recent first)",
        ge=1,
        le=100,
    )
    mark_as_read: bool = Field(
        default=False,
        description="Mark fetched emails as read (sets the \\Seen flag)",
    )
    include_attachment_content: bool = Field(
        default=False,
        description="Include base64-encoded attachment content in output (can be large)",
    )


class ReadEmailsOutput(BaseNodeOutput):
    """Output containing fetched emails."""

    emails: list[dict[str, Any]] = Field(..., description="List of email messages")
    count: int = Field(..., description="Number of emails fetched")
    folder: str = Field(..., description="Mailbox folder that was read")


class ReadEmailsNode(BaseNode):
    """Read emails from a mailbox via IMAP.

    Connects to an IMAP server and fetches emails from the specified folder.
    Supports filtering by IMAP search criteria (e.g., UNSEEN, FROM, SINCE).
    Returns email metadata, body content, and attachment info.
    """

    input_schema = ReadEmailsInput
    output_schema = ReadEmailsOutput
    credential_schema = IMAPCredentialSchema

    metadata = NodeMetadata(
        name="Read Emails",
        description="Read emails from a mailbox via IMAP",
        category="email",
        icon="📬",
        color="#4A90D9",
    )

    async def execute_logic(
        self,
        validated_inputs: dict[str, Any],
        context: NodeExecutionContext,
    ) -> ReadEmailsOutput:
        """Fetch emails using IMAP."""
        credential_data = await context.resolve_credential(
            credential_id=validated_inputs["credential_id"],
            credential_type=self.get_credential_type(),
        )

        context.register_secret(credential_data["password"])

        host = credential_data["host"]
        port = credential_data.get("port", 993)
        username = credential_data["username"]
        password = credential_data["password"]
        use_ssl = credential_data.get("use_ssl", True)
        readonly = credential_data.get("readonly", False)

        mark_as_read = validated_inputs.get("mark_as_read", False)
        if mark_as_read and readonly:
            raise ValueError("Cannot mark emails as read: credential is configured as readonly")

        folder = validated_inputs.get("folder", "INBOX")
        filter_criteria = validated_inputs.get("filter", "ALL")
        max_results = validated_inputs.get("max_results", 10)
        include_attachment_content = validated_inputs.get("include_attachment_content", False)

        try:
            emails = await asyncio.to_thread(
                _fetch_emails_sync,
                host=host,
                port=port,
                username=username,
                password=password,
                use_ssl=use_ssl,
                folder=folder,
                filter_criteria=filter_criteria,
                max_results=max_results,
                mark_as_read=mark_as_read,
                include_attachment_content=include_attachment_content,
                readonly=readonly,
            )
        except imaplib.IMAP4.error as e:
            raise RuntimeError(f"IMAP error: {e}") from e
        except OSError as e:
            raise RuntimeError(f"IMAP connection error: {e}") from e

        return ReadEmailsOutput(
            emails=emails,
            count=len(emails),
            folder=folder,
        )
