from __future__ import annotations

from dataclasses import dataclass
from datetime import UTC, datetime
from email.message import EmailMessage
from email.utils import formataddr
import smtplib

from app.config import Settings


class EmailConfigurationError(ValueError):
    """Raised when SMTP settings are incomplete or disabled."""


class EmailDeliveryError(RuntimeError):
    """Raised when sending email fails."""


@dataclass(frozen=True, slots=True)
class SMTPConfig:
    host: str
    port: int
    username: str
    password: str
    from_name: str
    from_address: str
    to_address: str
    use_starttls: bool


def get_smtp_config(settings: Settings, *, require_enabled: bool = True) -> SMTPConfig:
    if require_enabled and not settings.smtp_enabled:
        raise EmailConfigurationError("SMTP is disabled")

    if not settings.smtp_host:
        raise EmailConfigurationError("SMTP host is required")

    if settings.smtp_port <= 0:
        raise EmailConfigurationError("SMTP port must be greater than zero")

    if not settings.smtp_from_address:
        raise EmailConfigurationError("SMTP from address is required")

    if not settings.smtp_to_address:
        raise EmailConfigurationError("SMTP to address is required")

    return SMTPConfig(
        host=settings.smtp_host,
        port=settings.smtp_port,
        username=settings.smtp_username,
        password=settings.smtp_password,
        from_name=settings.smtp_from_name,
        from_address=settings.smtp_from_address,
        to_address=settings.smtp_to_address,
        use_starttls=settings.smtp_use_starttls,
    )


def is_smtp_ready(settings: Settings) -> bool:
    try:
        get_smtp_config(settings, require_enabled=False)
    except EmailConfigurationError:
        return False
    return True


def send_plaintext_email(
    settings: Settings,
    *,
    subject: str,
    body: str,
    recipient: str | None = None,
    require_enabled: bool = True,
) -> None:
    smtp_config = get_smtp_config(settings, require_enabled=require_enabled)
    message = EmailMessage()
    message["Subject"] = subject
    message["From"] = _build_from_header(smtp_config)
    message["To"] = recipient or smtp_config.to_address
    message.set_content(body)

    try:
        with smtplib.SMTP(smtp_config.host, smtp_config.port, timeout=10) as smtp:
            smtp.ehlo()
            if smtp_config.use_starttls:
                smtp.starttls()
                smtp.ehlo()
            if smtp_config.username:
                smtp.login(smtp_config.username, smtp_config.password)
            smtp.send_message(
                message,
                from_addr=smtp_config.from_address,
                to_addrs=[recipient or smtp_config.to_address],
            )
    except (OSError, smtplib.SMTPException) as exc:
        error_message = _sanitize_error_message(str(exc), smtp_config.password)
        raise EmailDeliveryError(error_message or "SMTP delivery failed") from exc


def send_smtp_test_email(settings: Settings) -> None:
    send_plaintext_email(
        settings,
        subject="Home Automation SMTP Test",
        body="This is a test email from Home Automation SMTP settings.",
        require_enabled=False,
    )


def send_public_ip_changed_email(
    settings: Settings,
    *,
    previous_ipv4: str,
    current_ipv4: str,
    detected_at: datetime,
) -> None:
    send_plaintext_email(
        settings,
        subject="Public IP changed",
        body=(
            "Your public IPv4 address has changed.\n\n"
            f"Previous IP: {previous_ipv4}\n"
            f"Current IP: {current_ipv4}\n"
            f"Detected at: {_format_utc_timestamp(detected_at)}\n\n"
            "If you use Namecheap API trusted IP restrictions, you may need to "
            "update the trusted IP manually.\n"
        ),
    )


def _sanitize_error_message(message: str, password: str) -> str:
    sanitized = message
    if password:
        sanitized = sanitized.replace(password, "[redacted]")
    return sanitized


def _format_utc_timestamp(value: datetime) -> str:
    if value.tzinfo is None:
        normalized = value.replace(tzinfo=UTC)
    else:
        normalized = value.astimezone(UTC)
    return normalized.strftime("%Y-%m-%d %H:%M:%S UTC")


def _build_from_header(smtp_config: SMTPConfig) -> str:
    if smtp_config.from_name:
        return formataddr((smtp_config.from_name, smtp_config.from_address))
    return smtp_config.from_address