# app/modules/messaging/models/email.py
"""
Email system database models.

Provides:
- EmailTemplate: Multi-language email templates stored in database
- EmailLog: Email sending history and tracking

Platform vs Store Templates:
- Platform templates (EmailTemplate) are the defaults
- Stores can override templates via StoreEmailTemplate
- Platform-only templates (is_platform_only=True) cannot be overridden
"""

import enum
import json
from datetime import datetime

from sqlalchemy import (
    Boolean,
    Column,
    DateTime,
    ForeignKey,
    Index,
    Integer,
    String,
    Text,
)
from sqlalchemy.orm import Session, relationship

from app.core.database import Base
from models.database.base import TimestampMixin


class EmailCategory(str, enum.Enum):
    """Email template categories."""

    AUTH = "auth"  # signup, password reset, verification
    ORDERS = "orders"  # order confirmations, shipping
    BILLING = "billing"  # invoices, payment failures
    SYSTEM = "system"  # team invites, notifications
    MARKETING = "marketing"  # newsletters, promotions


class EmailStatus(str, enum.Enum):
    """Email sending status."""

    PENDING = "pending"
    SENT = "sent"
    FAILED = "failed"
    BOUNCED = "bounced"
    DELIVERED = "delivered"
    OPENED = "opened"
    CLICKED = "clicked"


class EmailTemplate(Base, TimestampMixin):
    """
    Multi-language email templates.

    Templates use Jinja2 syntax for variable interpolation.
    Each template can have multiple language versions.
    """

    __tablename__ = "email_templates"

    id = Column(Integer, primary_key=True, index=True)

    # Template identification
    code = Column(String(100), nullable=False, index=True)  # e.g., "signup_welcome"
    language = Column(String(5), nullable=False, default="en")  # e.g., "en", "fr", "de", "lb"

    # Template metadata
    name = Column(String(255), nullable=False)  # Human-readable name
    description = Column(Text, nullable=True)  # Template purpose description
    category = Column(
        String(50), default=EmailCategory.SYSTEM.value, nullable=False, index=True
    )

    # Email content
    subject = Column(String(500), nullable=False)  # Subject line (supports variables)
    body_html = Column(Text, nullable=False)  # HTML body
    body_text = Column(Text, nullable=True)  # Plain text fallback

    # Template variables (JSON list of expected variables)
    # e.g., ["first_name", "merchant_name", "login_url"]
    variables = Column(Text, nullable=True)

    # Required variables (JSON list of variables that MUST be provided)
    # Subset of variables that are mandatory for the template to render
    required_variables = Column(Text, nullable=True)

    # Status
    is_active = Column(Boolean, default=True, nullable=False)

    # Platform-only flag: if True, stores cannot override this template
    # Used for billing, subscription, and other platform-level emails
    is_platform_only = Column(Boolean, default=False, nullable=False)

    # Unique constraint: one template per code+language
    __table_args__ = (
        Index("ix_email_templates_code_language", "code", "language", unique=True),
        {"sqlite_autoincrement": True},
    )

    def __repr__(self):
        return f"<EmailTemplate(code='{self.code}', language='{self.language}')>"

    @property
    def variables_list(self) -> list[str]:
        """Parse variables JSON to list."""
        if not self.variables:
            return []
        try:
            return json.loads(self.variables)
        except (json.JSONDecodeError, TypeError):
            return []

    @property
    def required_variables_list(self) -> list[str]:
        """Parse required_variables JSON to list."""
        if not self.required_variables:
            return []
        try:
            return json.loads(self.required_variables)
        except (json.JSONDecodeError, TypeError):
            return []

    @classmethod
    def get_by_code_and_language(
        cls,
        db: Session,
        code: str,
        language: str,
        fallback_to_english: bool = True,
    ) -> "EmailTemplate | None":
        """
        Get a platform template by code and language.

        Args:
            db: Database session
            code: Template code (e.g., "password_reset")
            language: Language code (en, fr, de, lb)
            fallback_to_english: If True, fall back to English if language not found

        Returns:
            EmailTemplate if found, None otherwise
        """
        template = (
            db.query(cls)
            .filter(
                cls.code == code,
                cls.language == language,
                cls.is_active == True,  # noqa: E712
            )
            .first()
        )

        # Fallback to English if requested language not found
        if not template and fallback_to_english and language != "en":
            template = (
                db.query(cls)
                .filter(
                    cls.code == code,
                    cls.language == "en",
                    cls.is_active == True,  # noqa: E712
                )
                .first()
            )

        return template

    @classmethod
    def get_all_templates(
        cls,
        db: Session,
        category: str | None = None,
        include_inactive: bool = False,
    ) -> list["EmailTemplate"]:
        """
        Get all platform templates, optionally filtered by category.

        Args:
            db: Database session
            category: Optional category filter
            include_inactive: Include inactive templates

        Returns:
            List of EmailTemplate objects
        """
        query = db.query(cls)

        if category:
            query = query.filter(cls.category == category)

        if not include_inactive:
            query = query.filter(cls.is_active == True)  # noqa: E712

        return query.order_by(cls.code, cls.language).all()

    @classmethod
    def get_overridable_templates(cls, db: Session) -> list["EmailTemplate"]:
        """
        Get all templates that stores can override.

        Returns:
            List of EmailTemplate objects where is_platform_only=False
        """
        return (
            db.query(cls)
            .filter(
                cls.is_platform_only == False,  # noqa: E712
                cls.is_active == True,  # noqa: E712
            )
            .order_by(cls.code, cls.language)
            .all()
        )


class EmailLog(Base, TimestampMixin):
    """
    Email sending history and tracking.

    Logs all sent emails for debugging, analytics, and compliance.
    """

    __tablename__ = "email_logs"

    id = Column(Integer, primary_key=True, index=True)

    # Template reference
    template_code = Column(String(100), nullable=True, index=True)
    template_id = Column(Integer, ForeignKey("email_templates.id"), nullable=True)

    # Recipient info
    recipient_email = Column(String(255), nullable=False, index=True)
    recipient_name = Column(String(255), nullable=True)

    # Email content (snapshot at send time)
    subject = Column(String(500), nullable=False)
    body_html = Column(Text, nullable=True)
    body_text = Column(Text, nullable=True)

    # Sending info
    from_email = Column(String(255), nullable=False)
    from_name = Column(String(255), nullable=True)
    reply_to = Column(String(255), nullable=True)

    # Status tracking
    status = Column(
        String(20), default=EmailStatus.PENDING.value, nullable=False, index=True
    )
    sent_at = Column(DateTime, nullable=True)
    delivered_at = Column(DateTime, nullable=True)
    opened_at = Column(DateTime, nullable=True)
    clicked_at = Column(DateTime, nullable=True)

    # Error handling
    error_message = Column(Text, nullable=True)
    retry_count = Column(Integer, default=0, nullable=False)

    # Provider info
    provider = Column(String(50), nullable=True)  # smtp, sendgrid, mailgun, ses
    provider_message_id = Column(String(255), nullable=True, index=True)

    # Context linking (optional - link to related entities)
    store_id = Column(Integer, ForeignKey("stores.id"), nullable=True, index=True)
    user_id = Column(Integer, ForeignKey("users.id"), nullable=True, index=True)
    related_type = Column(String(50), nullable=True)  # e.g., "order", "subscription"
    related_id = Column(Integer, nullable=True)

    # Extra data (JSON for additional context)
    extra_data = Column(Text, nullable=True)

    # Relationships
    template = relationship("EmailTemplate", foreign_keys=[template_id])
    store = relationship("Store", foreign_keys=[store_id])
    user = relationship("User", foreign_keys=[user_id])

    def __repr__(self):
        return f"<EmailLog(id={self.id}, recipient='{self.recipient_email}', status='{self.status}')>"

    def mark_sent(self, provider_message_id: str | None = None):
        """Mark email as sent."""
        self.status = EmailStatus.SENT.value
        self.sent_at = datetime.utcnow()
        if provider_message_id:
            self.provider_message_id = provider_message_id

    def mark_failed(self, error_message: str):
        """Mark email as failed."""
        self.status = EmailStatus.FAILED.value
        self.error_message = error_message
        self.retry_count += 1

    def mark_delivered(self):
        """Mark email as delivered."""
        self.status = EmailStatus.DELIVERED.value
        self.delivered_at = datetime.utcnow()

    def mark_opened(self):
        """Mark email as opened."""
        self.status = EmailStatus.OPENED.value
        self.opened_at = datetime.utcnow()


__all__ = ["EmailCategory", "EmailStatus", "EmailTemplate", "EmailLog"]
