"""Custom user model.

Mobile number is the primary login identifier; email is an optional secondary
identifier. Username is a non-unique optional display name.
"""

from __future__ import annotations

import uuid
from typing import ClassVar

from django.conf import settings
from django.contrib.auth.models import AbstractUser, BaseUserManager
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from simorgh.core.models import TimeStampedModel, UUIDModel


class UserManager(BaseUserManager["User"]):
    use_in_migrations = True

    def _create_user(self, mobile: str, password: str | None, **extra_fields: object) -> User:
        if not mobile:
            raise ValueError(_("A mobile number is required."))
        user = self.model(mobile=mobile, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, mobile: str, password: str | None = None, **extra_fields: object) -> User:
        extra_fields.setdefault("is_staff", False)
        extra_fields.setdefault("is_superuser", False)
        return self._create_user(mobile, password, **extra_fields)

    def create_superuser(
        self, mobile: str, password: str | None = None, **extra_fields: object
    ) -> User:
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)
        if extra_fields.get("is_staff") is not True:
            raise ValueError(_("Superuser must have is_staff=True."))
        if extra_fields.get("is_superuser") is not True:
            raise ValueError(_("Superuser must have is_superuser=True."))
        return self._create_user(mobile, password, **extra_fields)


class User(AbstractUser):
    """Mobile-based custom user model."""

    # username is a non-unique optional display name; mobile is the login key.
    username = models.CharField(_("display name"), max_length=150, blank=True)
    email = models.EmailField(
        _("email address"),
        null=True,
        blank=True,
        db_index=True,
    )
    mobile = models.CharField(
        _("mobile number"),
        max_length=20,
        unique=True,
        db_index=True,
        help_text=_("E.164 format, e.g. +989123456789"),
    )

    public_id = models.UUIDField(
        _("public id"),
        default=uuid.uuid4,
        editable=False,
        unique=True,
        db_index=True,
    )

    # Country code for mobile number — FK relation to localization.Country.
    mobile_country = models.ForeignKey(
        "localization.Country",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="+",
        verbose_name=_("country code"),
    )

    # Locale preferences — FK relations to the localization app.
    language = models.ForeignKey(
        "localization.Language",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="+",
        verbose_name=_("language"),
    )
    timezone = models.ForeignKey(
        "localization.Timezone",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="+",
        verbose_name=_("timezone"),
    )

    USERNAME_FIELD = "mobile"
    REQUIRED_FIELDS: ClassVar[list[str]] = []

    objects = UserManager()

    class Meta:
        verbose_name = _("user")
        verbose_name_plural = _("users")
        ordering = ("mobile",)

    def __str__(self) -> str:
        return self.mobile or self.email or str(self.pk)


class UserProfile(TimeStampedModel):
    """Extended profile data for a user.

    Kept separate from ``User`` to avoid bloating the auth model and to allow
    optional fields that not all deployments need.
    """

    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="profile",
        verbose_name=_("user"),
    )
    avatar = models.ForeignKey(
        "storage.FileMetadata",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="+",
        verbose_name=_("avatar"),
    )
    avatar_image = models.FileField(
        _("avatar image"),
        upload_to="avatars/",
        null=True,
        blank=True,
    )
    bio = models.TextField(_("bio"), blank=True, default="")
    phone = models.CharField(_("phone"), max_length=32, blank=True, default="")
    job_title = models.CharField(_("job title"), max_length=128, blank=True, default="")
    department = models.CharField(_("department"), max_length=128, blank=True, default="")
    linkedin_url = models.URLField(_("LinkedIn URL"), blank=True, default="")
    website_url = models.URLField(_("website URL"), blank=True, default="")

    class Meta:
        verbose_name = _("user profile")
        verbose_name_plural = _("user profiles")

    def __str__(self) -> str:
        return f"Profile({self.user_id})"


# ---------------------------------------------------------------------------
# IAM / Authentication configuration
# ---------------------------------------------------------------------------


class AuthMethodConfig(TimeStampedModel):
    """Per-tenant authentication method configuration.

    Stores which login methods are enabled for a given tenant.  Evaluated at
    login time so changes take effect immediately without redeployment.
    """

    class LoginMethod(models.TextChoices):
        PASSWORD = "password", _("Password (mobile)")
        MOBILE_OTP = "mobile_otp", _("Mobile OTP")
        ACTIVE_DIRECTORY = "active_directory", _("Active Directory / LDAP")
        GOOGLE = "google", _("Google OAuth")
        GITHUB = "github", _("GitHub OAuth")
        MICROSOFT = "microsoft", _("Microsoft OAuth")

    tenant = models.OneToOneField(
        "tenants.Tenant",
        on_delete=models.CASCADE,
        related_name="auth_method_config",
        verbose_name=_("tenant"),
    )
    is_password_enabled = models.BooleanField(_("password login"), default=True)
    is_mobile_otp_enabled = models.BooleanField(_("mobile OTP login"), default=False)
    is_ad_enabled = models.BooleanField(_("Active Directory login"), default=False)
    is_google_enabled = models.BooleanField(_("Google login"), default=False)
    is_github_enabled = models.BooleanField(_("GitHub login"), default=False)
    is_microsoft_enabled = models.BooleanField(_("Microsoft login"), default=False)

    class Meta:
        verbose_name = _("Auth method config")
        verbose_name_plural = _("Auth method configs")

    def __str__(self) -> str:
        return f"AuthMethodConfig({getattr(self, 'tenant_id', '?')})"


class ActiveDirectoryConfig(TimeStampedModel):
    """Per-tenant LDAP / Active Directory connection settings."""

    tenant = models.OneToOneField(
        "tenants.Tenant",
        on_delete=models.CASCADE,
        related_name="ad_config",
        verbose_name=_("tenant"),
    )
    server_url = models.CharField(
        _("LDAP server URL"),
        max_length=256,
        blank=True,
        default="",
        help_text=_("e.g. ldap://dc.example.com or ldaps://dc.example.com:636"),
    )
    domain = models.CharField(_("domain"), max_length=256, blank=True, default="")
    base_dn = models.CharField(
        _("base DN"),
        max_length=512,
        blank=True,
        default="",
        help_text=_("e.g. DC=example,DC=com"),
    )
    user_search_filter = models.CharField(
        _("user search filter"),
        max_length=256,
        default="(sAMAccountName={username})",
        help_text=_("Use {username} as placeholder for the login name."),
    )
    service_account_dn = models.CharField(
        _("service account DN"), max_length=512, blank=True, default=""
    )
    # NOTE: store encrypted in production via a custom field or secrets manager.
    service_account_password = models.CharField(
        _("service account password"), max_length=256, blank=True, default=""
    )
    is_active = models.BooleanField(_("active"), default=False)

    class Meta:
        verbose_name = _("Active Directory config")
        verbose_name_plural = _("Active Directory configs")

    def __str__(self) -> str:
        return f"ADConfig({self.domain or getattr(self, 'tenant_id', '?')})"


class SocialAuthConfig(TimeStampedModel):
    """Per-tenant OAuth2 / social login provider settings."""

    tenant = models.OneToOneField(
        "tenants.Tenant",
        on_delete=models.CASCADE,
        related_name="social_auth_config",
        verbose_name=_("tenant"),
    )
    # Google
    google_client_id = models.CharField(
        _("Google client ID"), max_length=256, blank=True, default=""
    )
    google_client_secret = models.CharField(
        _("Google client secret"), max_length=256, blank=True, default=""
    )
    google_enabled = models.BooleanField(_("Google enabled"), default=False)
    # GitHub
    github_client_id = models.CharField(
        _("GitHub client ID"), max_length=256, blank=True, default=""
    )
    github_client_secret = models.CharField(
        _("GitHub client secret"), max_length=256, blank=True, default=""
    )
    github_enabled = models.BooleanField(_("GitHub enabled"), default=False)
    # Microsoft
    microsoft_client_id = models.CharField(
        _("Microsoft client ID"), max_length=256, blank=True, default=""
    )
    microsoft_client_secret = models.CharField(
        _("Microsoft client secret"), max_length=256, blank=True, default=""
    )
    microsoft_enabled = models.BooleanField(_("Microsoft enabled"), default=False)

    class Meta:
        verbose_name = _("Social auth config")
        verbose_name_plural = _("Social auth configs")

    def __str__(self) -> str:
        return f"SocialAuthConfig({getattr(self, 'tenant_id', '?')})"


class MobileOtpToken(UUIDModel, TimeStampedModel):
    """Short-lived one-time-password for mobile phone authentication.

    ``public_id`` (from UUIDModel) is returned to the client as the opaque
    token reference; the plain code is stored in ``code``.
    """

    mobile = models.CharField(_("mobile"), max_length=20, db_index=True)
    code = models.CharField(_("code"), max_length=16)
    tenant = models.ForeignKey(
        "tenants.Tenant",
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name="+",
        verbose_name=_("tenant"),
    )
    expires_at = models.DateTimeField(_("expires at"))
    is_used = models.BooleanField(_("used"), default=False, db_index=True)
    attempts = models.PositiveSmallIntegerField(_("attempts"), default=0)

    class Meta:
        verbose_name = _("Mobile OTP token")
        verbose_name_plural = _("Mobile OTP tokens")
        indexes = [
            models.Index(fields=["mobile", "is_used", "expires_at"]),
        ]

    def __str__(self) -> str:
        return f"OTP({self.mobile} exp={self.expires_at.isoformat() if self.expires_at else '?'})"

    @property
    def is_expired(self) -> bool:
        return timezone.now() >= self.expires_at


class OtpSendLog(TimeStampedModel):
    """Audit log for every OTP send attempt.

    Recorded regardless of whether the SMS provider call succeeded so that
    admins can diagnose delivery failures and abuse patterns.
    """

    class SendStatus(models.TextChoices):
        SENT = "sent", _("Sent")
        FAILED = "failed", _("Failed")

    mobile = models.CharField(_("mobile"), max_length=20, db_index=True)
    otp_token = models.ForeignKey(
        MobileOtpToken,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="send_logs",
        verbose_name=_("OTP token"),
    )
    tenant = models.ForeignKey(
        "tenants.Tenant",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="+",
        verbose_name=_("tenant"),
    )
    provider = models.CharField(_("provider"), max_length=64, blank=True, default="")
    status = models.CharField(
        _("status"),
        max_length=10,
        choices=SendStatus.choices,
        default=SendStatus.SENT,
        db_index=True,
    )
    error_message = models.TextField(_("error message"), blank=True, default="")
    ip_address = models.GenericIPAddressField(_("IP address"), null=True, blank=True)

    class Meta:
        verbose_name = _("OTP send log")
        verbose_name_plural = _("OTP send logs")
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["mobile", "-created_at"]),
        ]

    def __str__(self) -> str:
        return f"OtpSendLog({self.mobile} {self.status} {self.created_at})"

