"""
Email dispatch service with Jinja2 HTML rendering and resilient SMTP error handling.
"""

import logging
from pathlib import Path
from typing import Any

from fastapi_mail import ConnectionConfig, FastMail, MessageSchema, MessageType
from jinja2 import Environment, FileSystemLoader, select_autoescape
from pydantic import EmailStr, TypeAdapter, ValidationError

from app.config import settings

_logger = logging.getLogger(__name__)
_email_adapter = TypeAdapter(EmailStr)


def normalize_email_address(value: str | None) -> str | None:
    """Return a validated, trimmed address or None for missing/invalid values."""
    cleaned = (value or "").strip()
    if not cleaned:
        return None
    try:
        return str(_email_adapter.validate_python(cleaned))
    except ValidationError:
        return None


class EmailService:
    """Renders HTML email templates and dispatches transactional emails."""

    def __init__(self, template_dir: Path | None = None) -> None:
        self.template_dir = template_dir or (Path(__file__).parent.parent / "templates" / "emails")
        self.jinja_env = Environment(
            loader=FileSystemLoader(str(self.template_dir)),
            autoescape=select_autoescape(["html", "xml"]),
            enable_async=False,
        )

    def render_template(self, template_name: str, context: dict[str, Any]) -> str:
        """Render a Jinja2 HTML template with the given context dictionary."""
        template = self.jinja_env.get_template(template_name)
        return template.render(**context)

    def _build_mail_config(
        self,
        *,
        host: str,
        port: int,
        username: str,
        password: str,
        mail_from: str,
        starttls: bool,
        ssl_tls: bool,
        use_credentials: bool,
    ) -> ConnectionConfig:
        return ConnectionConfig(
            MAIL_USERNAME=username,
            MAIL_PASSWORD=password,
            MAIL_FROM=mail_from,
            MAIL_PORT=port,
            MAIL_SERVER=host,
            MAIL_STARTTLS=starttls,
            MAIL_SSL_TLS=ssl_tls,
            USE_CREDENTIALS=use_credentials,
            VALIDATE_CERTS=not settings.APP_DEBUG,
        )

    def _get_production_mail_config(self) -> ConnectionConfig | None:
        """Construct ConnectionConfig for configured production SMTP."""
        if not settings.SMTP_HOST or settings.smtp_credentials_are_placeholder:
            return None
        return self._build_mail_config(
            host=settings.SMTP_HOST,
            port=settings.SMTP_PORT,
            username=settings.SMTP_USER,
            password=settings.effective_smtp_password,
            mail_from=settings.effective_smtp_from,
            starttls=settings.SMTP_TLS,
            ssl_tls=settings.SMTP_SSL,
            use_credentials=True,
        )

    def _get_mailpit_dev_config(self) -> ConnectionConfig:
        """Optional local Mailpit capture; view messages at http://localhost:8025."""
        return self._build_mail_config(
            host="localhost",
            port=1025,
            username="",
            password="",
            mail_from=settings.effective_smtp_from,
            starttls=False,
            ssl_tls=False,
            use_credentials=False,
        )

    async def _dispatch(
        self, config: ConnectionConfig, recipient: str, subject: str, html_content: str
    ) -> None:
        import asyncio

        message = MessageSchema(
            subject=subject,
            recipients=[recipient],
            body=html_content,
            subtype=MessageType.html,
        )
        fm = FastMail(config)
        await asyncio.wait_for(fm.send_message(message), timeout=10.0)

    async def send_email(
        self,
        recipient: str,
        subject: str,
        html_content: str,
        *,
        dev_otp: str | None = None,
    ) -> tuple[bool, str | None]:
        """
        Send an HTML email to a recipient.
        Returns (success: bool, error_message: str | None).
        Never raises unhandled exceptions to caller.
        """
        normalized_recipient = normalize_email_address(recipient)
        if not normalized_recipient:
            return False, "A valid recipient email address is required."
        recipient = normalized_recipient

        try:
            config = self._get_production_mail_config()
            if config is not None:
                await self._dispatch(config, recipient, subject, html_content)
                return True, None

            if settings.is_development and not settings.is_production:
                try:
                    await self._dispatch(
                        self._get_mailpit_dev_config(),
                        recipient,
                        subject,
                        html_content,
                    )
                    _logger.info(
                        "Password reset email captured by local Mailpit for %s (open http://localhost:8025)",
                        recipient,
                    )
                    return True, None
                except Exception as mailpit_exc:
                    _logger.warning("Mailpit dispatch failed (%s); SMTP may be offline.", mailpit_exc)

            if settings.APP_DEBUG and dev_otp:
                _logger.warning(
                    "[DEV ONLY] SMTP not configured — password reset code for %s: %s",
                    recipient,
                    dev_otp,
                )
                return True, None

            if settings.is_production:
                return False, "SMTP is not configured for production email delivery."

            return False, (
                "Email could not be sent. Configure SMTP_USER/SMTP_PASSWORD in .env for delivery to "
                "external inboxes (e.g. yopmail), or run Mailpit locally and open http://localhost:8025."
            )
        except Exception as exc:
            _logger.error("Failed to send email to %s: %s", recipient, exc, exc_info=True)
            error_text = str(exc).lower()
            authentication_failed = any(
                marker in error_text
                for marker in ("535", "badcredentials", "username and password not accepted")
            )
            if authentication_failed:
                return False, (
                    "Gmail rejected SMTP authentication. Check that SMTP_USER is the Gmail account "
                    "used to create the App Password, that 2-Step Verification is enabled, and that "
                    "SMTP_PASSWORD contains the current 16-character App Password. Spaces used to "
                    "group the password are removed automatically."
                )
            return False, str(exc)
