from __future__ import annotations

from typing import ClassVar

from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _

from simorgh.core.models import TenantScopedModel, TimeStampedModel, UUIDModel

# ---------------------------------------------------------------------------
# Country dial code choices for SmsProvider multi-select
# (ISO 3166-1 alpha-2 + ITU-T E.164 dial code)
# ---------------------------------------------------------------------------
COUNTRY_DIAL_CODE_CHOICES: list[tuple[str, str]] = [
    ("+93", "🇦🇫 Afghanistan (+93)"),
    ("+355", "🇦🇱 Albania (+355)"),
    ("+213", "🇩🇿 Algeria (+213)"),
    ("+376", "🇦🇩 Andorra (+376)"),
    ("+244", "🇦🇴 Angola (+244)"),
    ("+54", "🇦🇷 Argentina (+54)"),
    ("+374", "🇦🇲 Armenia (+374)"),
    ("+61", "🇦🇺 Australia (+61)"),
    ("+43", "🇦🇹 Austria (+43)"),
    ("+994", "🇦🇿 Azerbaijan (+994)"),
    ("+973", "🇧🇭 Bahrain (+973)"),
    ("+880", "🇧🇩 Bangladesh (+880)"),
    ("+375", "🇧🇾 Belarus (+375)"),
    ("+32", "🇧🇪 Belgium (+32)"),
    ("+501", "🇧🇿 Belize (+501)"),
    ("+229", "🇧🇯 Benin (+229)"),
    ("+975", "🇧🇹 Bhutan (+975)"),
    ("+591", "🇧🇴 Bolivia (+591)"),
    ("+387", "🇧🇦 Bosnia and Herzegovina (+387)"),
    ("+267", "🇧🇼 Botswana (+267)"),
    ("+55", "🇧🇷 Brazil (+55)"),
    ("+673", "🇧🇳 Brunei (+673)"),
    ("+359", "🇧🇬 Bulgaria (+359)"),
    ("+226", "🇧🇫 Burkina Faso (+226)"),
    ("+257", "🇧🇮 Burundi (+257)"),
    ("+855", "🇰🇭 Cambodia (+855)"),
    ("+237", "🇨🇲 Cameroon (+237)"),
    ("+1", "🇨🇦 Canada / US (+1)"),
    ("+238", "🇨🇻 Cape Verde (+238)"),
    ("+236", "🇨🇫 Central African Republic (+236)"),
    ("+235", "🇹🇩 Chad (+235)"),
    ("+56", "🇨🇱 Chile (+56)"),
    ("+86", "🇨🇳 China (+86)"),
    ("+57", "🇨🇴 Colombia (+57)"),
    ("+242", "🇨🇬 Congo (+242)"),
    ("+243", "🇨🇩 DR Congo (+243)"),
    ("+506", "🇨🇷 Costa Rica (+506)"),
    ("+385", "🇭🇷 Croatia (+385)"),
    ("+53", "🇨🇺 Cuba (+53)"),
    ("+357", "🇨🇾 Cyprus (+357)"),
    ("+420", "🇨🇿 Czech Republic (+420)"),
    ("+45", "🇩🇰 Denmark (+45)"),
    ("+253", "🇩🇯 Djibouti (+253)"),
    ("+593", "🇪🇨 Ecuador (+593)"),
    ("+20", "🇪🇬 Egypt (+20)"),
    ("+503", "🇸🇻 El Salvador (+503)"),
    ("+240", "🇬🇶 Equatorial Guinea (+240)"),
    ("+291", "🇪🇷 Eritrea (+291)"),
    ("+372", "🇪🇪 Estonia (+372)"),
    ("+251", "🇪🇹 Ethiopia (+251)"),
    ("+358", "🇫🇮 Finland (+358)"),
    ("+33", "🇫🇷 France (+33)"),
    ("+241", "🇬🇦 Gabon (+241)"),
    ("+220", "🇬🇲 Gambia (+220)"),
    ("+995", "🇬🇪 Georgia (+995)"),
    ("+49", "🇩🇪 Germany (+49)"),
    ("+233", "🇬🇭 Ghana (+233)"),
    ("+30", "🇬🇷 Greece (+30)"),
    ("+502", "🇬🇹 Guatemala (+502)"),
    ("+224", "🇬🇳 Guinea (+224)"),
    ("+245", "🇬🇼 Guinea-Bissau (+245)"),
    ("+592", "🇬🇾 Guyana (+592)"),
    ("+509", "🇭🇹 Haiti (+509)"),
    ("+504", "🇭🇳 Honduras (+504)"),
    ("+852", "🇭🇰 Hong Kong (+852)"),
    ("+36", "🇭🇺 Hungary (+36)"),
    ("+354", "🇮🇸 Iceland (+354)"),
    ("+91", "🇮🇳 India (+91)"),
    ("+62", "🇮🇩 Indonesia (+62)"),
    ("+98", "🇮🇷 Iran (+98)"),
    ("+964", "🇮🇶 Iraq (+964)"),
    ("+353", "🇮🇪 Ireland (+353)"),
    ("+972", "🇮🇱 Israel (+972)"),
    ("+39", "🇮🇹 Italy (+39)"),
    ("+81", "🇯🇵 Japan (+81)"),
    ("+962", "🇯🇴 Jordan (+962)"),
    ("+7", "🇰🇿 Kazakhstan (+7)"),
    ("+254", "🇰🇪 Kenya (+254)"),
    ("+82", "🇰🇷 South Korea (+82)"),
    ("+965", "🇰🇼 Kuwait (+965)"),
    ("+996", "🇰🇬 Kyrgyzstan (+996)"),
    ("+856", "🇱🇦 Laos (+856)"),
    ("+371", "🇱🇻 Latvia (+371)"),
    ("+961", "🇱🇧 Lebanon (+961)"),
    ("+266", "🇱🇸 Lesotho (+266)"),
    ("+231", "🇱🇷 Liberia (+231)"),
    ("+218", "🇱🇾 Libya (+218)"),
    ("+370", "🇱🇹 Lithuania (+370)"),
    ("+352", "🇱🇺 Luxembourg (+352)"),
    ("+853", "🇲🇴 Macau (+853)"),
    ("+261", "🇲🇬 Madagascar (+261)"),
    ("+265", "🇲🇼 Malawi (+265)"),
    ("+60", "🇲🇾 Malaysia (+60)"),
    ("+960", "🇲🇻 Maldives (+960)"),
    ("+223", "🇲🇱 Mali (+223)"),
    ("+356", "🇲🇹 Malta (+356)"),
    ("+222", "🇲🇷 Mauritania (+222)"),
    ("+230", "🇲🇺 Mauritius (+230)"),
    ("+52", "🇲🇽 Mexico (+52)"),
    ("+373", "🇲🇩 Moldova (+373)"),
    ("+377", "🇲🇨 Monaco (+377)"),
    ("+976", "🇲🇳 Mongolia (+976)"),
    ("+382", "🇲🇪 Montenegro (+382)"),
    ("+212", "🇲🇦 Morocco (+212)"),
    ("+258", "🇲🇿 Mozambique (+258)"),
    ("+95", "🇲🇲 Myanmar (+95)"),
    ("+264", "🇳🇦 Namibia (+264)"),
    ("+977", "🇳🇵 Nepal (+977)"),
    ("+31", "🇳🇱 Netherlands (+31)"),
    ("+64", "🇳🇿 New Zealand (+64)"),
    ("+505", "🇳🇮 Nicaragua (+505)"),
    ("+227", "🇳🇪 Niger (+227)"),
    ("+234", "🇳🇬 Nigeria (+234)"),
    ("+47", "🇳🇴 Norway (+47)"),
    ("+968", "🇴🇲 Oman (+968)"),
    ("+92", "🇵🇰 Pakistan (+92)"),
    ("+970", "🇵🇸 Palestinian Territory (+970)"),
    ("+507", "🇵🇦 Panama (+507)"),
    ("+675", "🇵🇬 Papua New Guinea (+675)"),
    ("+595", "🇵🇾 Paraguay (+595)"),
    ("+51", "🇵🇪 Peru (+51)"),
    ("+63", "🇵🇭 Philippines (+63)"),
    ("+48", "🇵🇱 Poland (+48)"),
    ("+351", "🇵🇹 Portugal (+351)"),
    ("+974", "🇶🇦 Qatar (+974)"),
    ("+40", "🇷🇴 Romania (+40)"),
    ("+7", "🇷🇺 Russia (+7)"),
    ("+250", "🇷🇼 Rwanda (+250)"),
    ("+966", "🇸🇦 Saudi Arabia (+966)"),
    ("+221", "🇸🇳 Senegal (+221)"),
    ("+381", "🇷🇸 Serbia (+381)"),
    ("+232", "🇸🇱 Sierra Leone (+232)"),
    ("+65", "🇸🇬 Singapore (+65)"),
    ("+421", "🇸🇰 Slovakia (+421)"),
    ("+386", "🇸🇮 Slovenia (+386)"),
    ("+252", "🇸🇴 Somalia (+252)"),
    ("+27", "🇿🇦 South Africa (+27)"),
    ("+34", "🇪🇸 Spain (+34)"),
    ("+94", "🇱🇰 Sri Lanka (+94)"),
    ("+249", "🇸🇩 Sudan (+249)"),
    ("+597", "🇸🇷 Suriname (+597)"),
    ("+268", "🇸🇿 Swaziland (+268)"),
    ("+46", "🇸🇪 Sweden (+46)"),
    ("+41", "🇨🇭 Switzerland (+41)"),
    ("+963", "🇸🇾 Syria (+963)"),
    ("+886", "🇹🇼 Taiwan (+886)"),
    ("+992", "🇹🇯 Tajikistan (+992)"),
    ("+255", "🇹🇿 Tanzania (+255)"),
    ("+66", "🇹🇭 Thailand (+66)"),
    ("+228", "🇹🇬 Togo (+228)"),
    ("+216", "🇹🇳 Tunisia (+216)"),
    ("+90", "🇹🇷 Turkey (+90)"),
    ("+993", "🇹🇲 Turkmenistan (+993)"),
    ("+256", "🇺🇬 Uganda (+256)"),
    ("+380", "🇺🇦 Ukraine (+380)"),
    ("+971", "🇦🇪 UAE (+971)"),
    ("+44", "🇬🇧 United Kingdom (+44)"),
    ("+1", "🇺🇸 United States (+1)"),
    ("+598", "🇺🇾 Uruguay (+598)"),
    ("+998", "🇺🇿 Uzbekistan (+998)"),
    ("+58", "🇻🇪 Venezuela (+58)"),
    ("+84", "🇻🇳 Vietnam (+84)"),
    ("+967", "🇾🇪 Yemen (+967)"),
    ("+260", "🇿🇲 Zambia (+260)"),
    ("+263", "🇿🇼 Zimbabwe (+263)"),
]


class NotificationChannel(models.TextChoices):
    INBOX = "inbox", _("Inbox")
    EMAIL = "email", _("Email")
    SMS = "sms", _("SMS")
    WEBSOCKET = "websocket", _("WebSocket")


class NotificationDeliveryStatus(models.TextChoices):
    PENDING = "pending", _("Pending")
    DELIVERED = "delivered", _("Delivered")
    FAILED = "failed", _("Failed")
    DEAD = "dead", _("Dead")


class Notification(UUIDModel, TenantScopedModel):
    """A single notification delivered (or queued for delivery) to a user."""

    recipient = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="notifications",
    )
    kind = models.CharField(_("kind"), max_length=128, db_index=True)
    channel = models.CharField(
        _("channel"),
        max_length=16,
        choices=NotificationChannel.choices,
        default=NotificationChannel.INBOX,
        db_index=True,
    )
    title = models.CharField(_("title"), max_length=256)
    body = models.TextField(_("body"), blank=True)
    payload = models.JSONField(_("payload"), default=dict, blank=True)
    delivered_at = models.DateTimeField(_("delivered at"), null=True, blank=True)
    read_at = models.DateTimeField(_("read at"), null=True, blank=True)
    status = models.CharField(
        _("status"),
        max_length=16,
        choices=NotificationDeliveryStatus.choices,
        default=NotificationDeliveryStatus.PENDING,
        db_index=True,
    )
    attempt = models.PositiveIntegerField(_("attempt"), default=0)
    max_attempts = models.PositiveIntegerField(_("max attempts"), default=3)
    last_error = models.TextField(_("last error"), blank=True)

    class Meta:
        verbose_name = _("notification")
        verbose_name_plural = _("notifications")
        ordering: ClassVar[tuple[str, ...]] = ("-created_at",)
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("tenant", "recipient", "-created_at")),
            models.Index(fields=("tenant", "recipient", "read_at")),
            models.Index(fields=("status", "channel")),
        ]

    def __str__(self) -> str:
        return f"{self.kind} → {self.recipient_id}"


class NotificationPreference(UUIDModel, TenantScopedModel, TimeStampedModel):
    """Per-user opt-in/opt-out by (kind, channel).

    Absence of a row means "use template default". A row with ``enabled=False``
    suppresses delivery on that channel.
    """

    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="notification_preferences",
    )
    kind = models.CharField(_("kind"), max_length=128, db_index=True)
    channel = models.CharField(
        _("channel"),
        max_length=16,
        choices=NotificationChannel.choices,
    )
    enabled = models.BooleanField(_("enabled"), default=True)

    class Meta:
        verbose_name = _("notification preference")
        verbose_name_plural = _("notification preferences")
        constraints: ClassVar[list[models.BaseConstraint]] = [
            models.UniqueConstraint(
                fields=("tenant", "user", "kind", "channel"),
                name="uniq_notification_pref_per_user",
            ),
        ]


# ---------------------------------------------------------------------------
# SMS Provider models
# ---------------------------------------------------------------------------


class SmsDeliveryStatus(models.TextChoices):
    PENDING = "pending", _("Pending")
    SENT = "sent", _("Sent")
    DELIVERED = "delivered", _("Delivered")
    FAILED = "failed", _("Failed")


class SmsProvider(UUIDModel, TimeStampedModel):
    """Registered SMS gateway provider.

    Each provider defines its own credentials, templates, and the set of
    country dial codes it handles.  Empty ``supported_country_codes`` means
    the provider handles *all* countries (international fallback).
    """

    name = models.CharField(_("name"), max_length=128)
    slug = models.SlugField(
        _("slug"),
        max_length=64,
        unique=True,
        help_text=_("Machine-readable identifier, e.g. 'sms_ir' or 'twilio'"),
    )
    description = models.TextField(_("description"), blank=True)
    is_active = models.BooleanField(_("is active"), default=True, db_index=True)

    # Provider-specific secrets: api_key, auth_token, line_number, etc.
    credentials = models.JSONField(
        _("credentials"),
        default=dict,
        blank=True,
        help_text=_(
            "JSON object with provider credentials. "
            "Example: {\"api_key\": \"xxx\", \"line_number\": \"30004505001175\"}"
        ),
    )

    # Provider-specific behaviour knobs: otp_template_id, otp_param_name, etc.
    extra_params = models.JSONField(
        _("extra parameters"),
        default=dict,
        blank=True,
        help_text=_(
            "JSON object with provider-specific parameters. "
            "Example: {\"otp_template_id\": 453080, \"otp_param_name\": \"Code\"}"
        ),
    )

    # Dial codes this provider handles (e.g. ["+98"]).
    # An empty list means "all countries" (used as international fallback).
    supported_country_codes = models.JSONField(
        _("supported country codes"),
        default=list,
        blank=True,
        help_text=_(
            "List of E.164 dial codes this provider handles. "
            "Example: [\"+98\"] for Iran only. Empty list = all countries."
        ),
    )

    priority = models.PositiveIntegerField(
        _("priority"),
        default=0,
        help_text=_("Higher value = preferred when multiple providers match the same country."),
    )

    class Meta:
        verbose_name = _("SMS provider")
        verbose_name_plural = _("SMS providers")
        ordering: ClassVar[list[str]] = ["-priority", "name"]

    def __str__(self) -> str:
        active_tag = "" if self.is_active else " [inactive]"
        return f"{self.name}{active_tag}"

    def get_supported_country_codes(self) -> list[str]:
        """Return the list of supported dial codes (may be empty = all)."""
        val = self.supported_country_codes
        if isinstance(val, list):
            return val
        return []


class SmsProviderTemplate(UUIDModel, TimeStampedModel):
    """Named template associated with an SMS provider.

    Providers that rely on pre-approved templates (e.g. SMS.ir, Kavenegar)
    reference them by a numeric or string ID.
    """

    provider = models.ForeignKey(
        SmsProvider,
        on_delete=models.CASCADE,
        related_name="templates",
        verbose_name=_("provider"),
    )
    name = models.CharField(
        _("name"),
        max_length=128,
        help_text=_("Internal label, e.g. 'otp', 'invoice_notification'"),
    )
    provider_template_id = models.CharField(
        _("provider template ID"),
        max_length=64,
        help_text=_("Template ID as defined on the provider's platform"),
    )
    # Ordered list of parameter names expected by this template
    params_schema = models.JSONField(
        _("parameters schema"),
        default=list,
        blank=True,
        help_text=_("List of parameter names required by this template. Example: [\"Code\"]"),
    )
    is_default_otp = models.BooleanField(
        _("is default OTP template"),
        default=False,
        help_text=_("Mark this as the default template for OTP sends on this provider."),
    )

    class Meta:
        verbose_name = _("SMS provider template")
        verbose_name_plural = _("SMS provider templates")
        constraints: ClassVar[list[models.BaseConstraint]] = [
            models.UniqueConstraint(
                fields=("provider", "name"),
                name="uniq_sms_provider_template_name",
            ),
        ]

    def __str__(self) -> str:
        return f"{self.provider.slug} / {self.name} ({self.provider_template_id})"


class SmsDeliveryLog(UUIDModel, TimeStampedModel):
    """Detailed log of every SMS send attempt via any provider.

    Tenant is nullable so system-level (OTP, admin) sends can also be logged.
    Admins can filter by provider, status, tenant, mobile prefix, and date.
    """

    provider = models.ForeignKey(
        SmsProvider,
        on_delete=models.PROTECT,
        related_name="delivery_logs",
        verbose_name=_("provider"),
    )
    tenant = models.ForeignKey(
        "tenants.Tenant",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="sms_delivery_logs",
        verbose_name=_("tenant"),
    )
    mobile = models.CharField(_("mobile number"), max_length=32, db_index=True)
    message_text = models.TextField(_("message text"), blank=True)
    template_id = models.CharField(_("template ID"), max_length=64, blank=True)
    status = models.CharField(
        _("status"),
        max_length=16,
        choices=SmsDeliveryStatus.choices,
        default=SmsDeliveryStatus.PENDING,
        db_index=True,
    )
    provider_message_id = models.CharField(
        _("provider message ID"),
        max_length=128,
        blank=True,
        db_index=True,
    )
    cost = models.DecimalField(
        _("cost"),
        max_digits=10,
        decimal_places=4,
        null=True,
        blank=True,
    )
    error_message = models.TextField(_("error message"), blank=True)
    raw_response = models.JSONField(_("raw response"), default=dict, blank=True)

    class Meta:
        verbose_name = _("SMS delivery log")
        verbose_name_plural = _("SMS delivery logs")
        ordering: ClassVar[list[str]] = ["-created_at"]
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("tenant", "-created_at")),
            models.Index(fields=("provider", "status", "-created_at")),
            models.Index(fields=("mobile", "-created_at")),
        ]

    def __str__(self) -> str:
        return f"{self.mobile} via {self.provider.slug} [{self.status}] {self.created_at:%Y-%m-%d %H:%M}"


class TenantSmsRateLimit(UUIDModel, TimeStampedModel):
    """Per-tenant SMS sending rate limits.

    Enforced by ``simorgh.apps.notifications.sms.rate_limiter`` using Redis
    rolling-window counters.  Zero (0) disables the respective limit.
    """

    tenant = models.OneToOneField(
        "tenants.Tenant",
        on_delete=models.CASCADE,
        related_name="sms_rate_limit",
        verbose_name=_("tenant"),
    )
    max_per_minute = models.PositiveIntegerField(
        _("max per minute"),
        default=10,
        help_text=_("Maximum SMS allowed per minute per tenant. 0 = unlimited."),
    )
    max_per_hour = models.PositiveIntegerField(
        _("max per hour"),
        default=100,
        help_text=_("Maximum SMS allowed per hour per tenant. 0 = unlimited."),
    )
    max_per_day = models.PositiveIntegerField(
        _("max per day"),
        default=1000,
        help_text=_("Maximum SMS allowed per day per tenant. 0 = unlimited."),
    )
    is_active = models.BooleanField(
        _("rate limiting active"),
        default=True,
        help_text=_("Disable to bypass rate limits for this tenant (use with caution)."),
    )

    class Meta:
        verbose_name = _("tenant SMS rate limit")
        verbose_name_plural = _("tenant SMS rate limits")

    def __str__(self) -> str:
        return f"{self.tenant} — {self.max_per_day}/day"


# ---------------------------------------------------------------------------
# Quiet Hours
# ---------------------------------------------------------------------------


class UserQuietHours(UUIDModel, TenantScopedModel, TimeStampedModel):
    """Per-user do-not-disturb window for non-critical notifications.

    When the current server time (in ``timezone``) falls between
    ``start`` and ``end``, non-critical notifications are queued with status
    ``pending`` instead of being delivered immediately.
    """

    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="quiet_hours",
        verbose_name=_("user"),
    )
    is_enabled = models.BooleanField(_("enabled"), default=False)
    start = models.TimeField(_("start time"), help_text=_("e.g. 23:00"))
    end = models.TimeField(_("end time"), help_text=_("e.g. 07:00"))
    timezone = models.CharField(
        _("timezone"),
        max_length=64,
        default="UTC",
        help_text=_("IANA timezone name, e.g. 'Asia/Tehran'"),
    )

    class Meta:
        verbose_name = _("user quiet hours")
        verbose_name_plural = _("user quiet hours")

    def __str__(self) -> str:
        return f"{self.user} quiet {self.start}–{self.end} ({self.timezone})"


# ---------------------------------------------------------------------------
# Web Push Subscription
# ---------------------------------------------------------------------------


class PushSubscription(UUIDModel, TenantScopedModel, TimeStampedModel):
    """A browser/device Web Push subscription endpoint for a user."""

    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="push_subscriptions",
        verbose_name=_("user"),
    )
    endpoint = models.URLField(_("endpoint"), max_length=2048)
    p256dh_key = models.CharField(_("p256dh key"), max_length=256)
    auth_key = models.CharField(_("auth key"), max_length=128)
    device_name = models.CharField(_("device name"), max_length=128, blank=True)
    is_active = models.BooleanField(_("is active"), default=True, db_index=True)
    last_used_at = models.DateTimeField(_("last used at"), null=True, blank=True)

    class Meta:
        verbose_name = _("push subscription")
        verbose_name_plural = _("push subscriptions")
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("tenant", "user", "is_active")),
        ]

    def __str__(self) -> str:
        return f"{self.user} — {self.device_name or self.endpoint[:40]}"

