"""Email message data models."""

from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from typing import Any

from pydantic import BaseModel
from pydantic import ConfigDict
from pydantic import EmailStr
from pydantic import Field
from pydantic import field_validator
from pydantic import model_validator


class Attachment(BaseModel):
    """Represents an email attachment."""

    filename: str = Field(..., description='Attachment filename')
    content: bytes = Field(..., description='File content as bytes')
    mimetype: str = Field(..., description='MIME type (e.g., application/pdf)')
    content_id: str | None = Field(default=None, description='Content-ID for inline images (used in cid: URLs)')

    model_config = ConfigDict(arbitrary_types_allowed=True)


class EmailMessage(BaseModel):
    """Represents an email message."""

    subject: str = Field(..., description='Email subject line')
    body: str = Field(default='', description='Plain text body (optional if template_id is provided)')
    from_email: EmailStr | str = Field(..., description='Sender email address')
    to: list[EmailStr | str] = Field(..., description='Primary recipients')
    cc: list[EmailStr | str] = Field(default_factory=list, description='Carbon copy recipients')
    bcc: list[EmailStr | str] = Field(default_factory=list, description='Blind carbon copy recipients')
    reply_to: list[EmailStr | str] = Field(default_factory=list, description='Reply-to addresses')
    attachments: list[Attachment] = Field(default_factory=list, description='File attachments')
    headers: dict[str, str] = Field(default_factory=dict, description='Custom email headers')
    html_body: str | None = Field(default=None, description='HTML version of body')
    tags: list[str] = Field(default_factory=list, description='Message tags for categorization and filtering')
    metadata: dict[str, str] = Field(default_factory=dict, description='Custom key-value metadata for tracking')
    template_id: str | None = Field(default=None, description='ESP template ID for templated emails')
    merge_data: dict[str, dict[str, Any]] | None = Field(
        default=None, description='Per-recipient template variables (recipient email -> variables)'
    )
    merge_global_data: dict[str, Any] | None = Field(
        default=None, description='Global template variables for all recipients'
    )
    track_opens: bool = Field(
        default=False, description='Enable open tracking (ESP inserts tracking pixel in HTML emails)'
    )
    track_clicks: bool = Field(default=False, description='Enable click tracking (ESP rewrites URLs to track clicks)')

    model_config = ConfigDict(extra='allow')

    @field_validator('to', 'cc', 'bcc', 'reply_to', mode='before')
    @classmethod
    def ensure_list(cls, v: Any) -> list[str]:
        """Ensure email fields are lists."""
        if isinstance(v, str):
            return [v]
        return v or []

    @model_validator(mode='after')
    def validate_body_or_template(self) -> 'EmailMessage':
        """Ensure either body or template_id is provided."""
        if not self.body and not self.template_id:
            msg = 'Either body or template_id must be provided'
            raise ValueError(msg)
        return self

    def as_mime(self) -> MIMEMultipart | MIMEText:
        """Convert EmailMessage to MIME message for SMTP sending."""
        from email import encoders

        # Determine message structure
        has_html = self.html_body is not None
        inline_attachments = [a for a in self.attachments if a.content_id]
        regular_attachments = [a for a in self.attachments if not a.content_id]
        has_inline = len(inline_attachments) > 0
        has_regular = len(regular_attachments) > 0

        msg: MIMEMultipart | MIMEText

        # Build the body part (text or alternative)
        body_part: MIMEMultipart | MIMEText
        if has_html:
            body_part = MIMEMultipart('alternative')
            body_part.attach(MIMEText(self.body, 'plain', 'utf-8'))
            body_part.attach(MIMEText(self.html_body or '', 'html', 'utf-8'))
        else:
            body_part = MIMEText(self.body, 'plain', 'utf-8')

        # Wrap with related if inline attachments exist
        content_part: MIMEMultipart | MIMEText
        if has_inline:
            related_part = MIMEMultipart('related')
            related_part.attach(body_part)
            for attachment in inline_attachments:
                part = MIMEBase(*attachment.mimetype.split('/', 1))
                part.set_payload(attachment.content)
                encoders.encode_base64(part)
                part.add_header('Content-Disposition', 'inline', filename=attachment.filename)
                part.add_header('Content-ID', f'<{attachment.content_id}>')
                related_part.attach(part)
            content_part = related_part
        else:
            content_part = body_part

        # Wrap with mixed if regular attachments exist
        if has_regular:
            msg = MIMEMultipart('mixed')
            msg.attach(content_part)
            for attachment in regular_attachments:
                part = MIMEBase(*attachment.mimetype.split('/', 1))
                part.set_payload(attachment.content)
                encoders.encode_base64(part)
                part.add_header('Content-Disposition', f'attachment; filename="{attachment.filename}"')
                msg.attach(part)
        else:
            msg = content_part

        # Set headers
        msg['Subject'] = self.subject
        msg['From'] = self.from_email
        msg['To'] = ', '.join(str(addr) for addr in self.to)

        if self.cc:
            msg['Cc'] = ', '.join(str(addr) for addr in self.cc)

        if self.reply_to:
            msg['Reply-To'] = ', '.join(str(addr) for addr in self.reply_to)

        # Add custom headers
        for key, value in self.headers.items():
            msg[key] = value

        return msg

    def recipients(self) -> list[str]:
        """Get all recipients (to, cc, bcc)."""
        return [str(addr) for addr in self.to + self.cc + self.bcc]

    def get_template_data(self, recipient: str) -> dict[str, Any]:
        """
        Get merged template data for a specific recipient.

        Combines merge_global_data with recipient-specific merge_data.
        Recipient-specific data takes precedence over global data.

        Args:
            recipient: Email address of the recipient

        Returns:
            Dictionary of template variables for the recipient
        """
        data: dict[str, Any] = {}

        # Start with global data
        if self.merge_global_data:
            data.update(self.merge_global_data)

        # Override with recipient-specific data
        if self.merge_data and recipient in self.merge_data:
            data.update(self.merge_data[recipient])

        return data
