from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils import timezone

from apps.core.authentication.managers import CustomUserManager
from apps.core.permissions.roles import UserRole
from apps.core.storage.paths import upload_to_operations


class Institution(models.Model):
    """Member / partner institution — enriched by RIMS operations (Phase 1)."""

    class InstitutionType(models.TextChoices):
        UNIVERSITY = "university", "University"
        RESEARCH = "research", "Research institute"
        OTHER = "other", "Other"

    name = models.CharField(max_length=255, unique=True)
    country = models.CharField(max_length=100, blank=True, help_text="Full country name, optional")
    institution_type = models.CharField(
        max_length=32,
        choices=InstitutionType.choices,
        default=InstitutionType.UNIVERSITY,
        blank=True,
    )
    region = models.CharField(max_length=128, blank=True)
    website = models.URLField(blank=True)
    logo = models.FileField(upload_to=upload_to_operations, blank=True, null=True)
    address = models.TextField(blank=True)
    is_member = models.BooleanField(
        default=False,
        help_text="RUFORUM network member (e.g. one of 175 universities)",
        db_index=True,
    )
    is_active = models.BooleanField(default=True, db_index=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["name"]
        indexes = [
            models.Index(fields=["is_member", "is_active", "country"]),
        ]

    def __str__(self):
        return self.name


class CustomUser(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True)
    first_name = models.CharField(max_length=150, blank=True)
    last_name = models.CharField(max_length=150, blank=True)
    phone = models.CharField(max_length=32, blank=True)
    institution = models.ForeignKey(
        Institution,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="users",
    )
    role = models.CharField(
        max_length=32,
        choices=UserRole.choices,
        blank=True,
        db_index=True,
    )
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    date_joined = models.DateTimeField(default=timezone.now)
    email_verified = models.BooleanField(default=False)
    # WS5.1 / FRREP-UR003 — non-partner free-text institution path. When the
    # registrant types a name not in the partner dropdown, we capture it here
    # and flag the account for admin review before granting full role access.
    non_partner_institution_name = models.CharField(
        max_length=255,
        blank=True,
        help_text="Self-reported institution name when not in the RUFORUM partner list.",
    )
    pending_admin_review = models.BooleanField(
        default=False,
        db_index=True,
        help_text="Account requires administrator review (e.g. non-partner institution registrations).",
    )
    # Import idempotency anchors. A person may be imported from more than one
    # source; resolution is email-keyed (see docs/smehub-migration-plan.md).
    external_ref = models.PositiveBigIntegerField(
        null=True,
        blank=True,
        unique=True,
        db_index=True,
        help_text="Reference id from the SME-Hub import `users` table.",
    )
    lms_ref = models.PositiveBigIntegerField(
        null=True,
        blank=True,
        unique=True,
        db_index=True,
        help_text="Reference id from the learning-platform import `mdl_user` table.",
    )
    external_alias = models.CharField(
        max_length=255,
        blank=True,
        help_text="Imported `users.alias` handle, preserved for reference.",
    )

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS: list[str] = []

    objects = CustomUserManager()

    class Meta:
        verbose_name = "user"
        verbose_name_plural = "users"
        permissions = [
            ("manage_users", "Can manage users, roles, and direct user permissions"),
            ("manage_role_permissions", "Can assign Django permissions to role groups"),
            (
                "view_rep_job_applications",
                "Can view job applications and CV snapshots for REP career postings",
            ),
        ]

    def __str__(self):
        full = " ".join(p for p in (self.first_name, self.last_name) if p).strip()
        return f"{full} — {self.email}" if full else self.email

    def get_full_name(self):
        parts = [self.first_name, self.last_name]
        return " ".join(p for p in parts if p).strip() or self.email

    @property
    def avatar_url(self) -> str:
        """Public URL for profile photo, or empty string (templates use initials fallback)."""
        try:
            profile = self.profile
        except ObjectDoesNotExist:
            return ""
        if profile.avatar:
            return profile.avatar.url
        return ""


class UserProfile(models.Model):
    class DegreeLevel(models.TextChoices):
        UNDERGRADUATE = "undergraduate", "Undergraduate"
        MASTERS = "masters", "Master's"
        PHD = "phd", "PhD / Doctorate"
        OTHER = "other", "Other"

    class Gender(models.TextChoices):
        FEMALE = "female", "Female"
        MALE = "male", "Male"
        NON_BINARY = "non_binary", "Non-binary"
        PREFER_NOT_TO_SAY = "prefer_not_to_say", "Prefer not to say"
        OTHER = "other", "Other"

    # Youth threshold for the AR010 women & youth participation indicator.
    # African Union youth definition is 15–35 inclusive; the indicator counts
    # learners ≤ this threshold (in years) as "youth".
    YOUTH_AGE_THRESHOLD = 35

    user = models.OneToOneField(CustomUser, on_delete=models.CASCADE, related_name="profile")
    bio = models.TextField(blank=True)
    avatar = models.ImageField(upload_to="core/avatars/%Y/%m/", blank=True)
    country = models.CharField(max_length=100, blank=True)
    city = models.CharField(max_length=120, blank=True)
    degree_level = models.CharField(
        max_length=32,
        choices=DegreeLevel.choices,
        blank=True,
        help_text="Highest relevant degree (used for grant eligibility rules).",
    )
    gender = models.CharField(
        max_length=32,
        choices=Gender.choices,
        blank=True,
        db_index=True,
        help_text="Self-reported, optional. Used for the AR010 women & youth participation indicator.",
    )
    date_of_birth = models.DateField(
        null=True,
        blank=True,
        help_text="Used to derive the youth participation indicator (AR010). Not displayed publicly.",
    )
    # WS5.2 / FRREP-UR006 — expanded professional profile fields.
    # Free-text and JSON shapes are deliberately loose — the PRD wants people
    # to write in their own words and we don't want to gate signup on a rigid
    # competency taxonomy that doesn't exist yet.
    qualifications = models.JSONField(
        default=list,
        blank=True,
        help_text=(
            "List of {id, institution, qualification, field_of_study, year} dicts. "
            "Repeatable entries edited via the profile page's popup add/edit UI — "
            "each dict carries a stable `id` (uuid4 hex) so entries can be "
            "individually updated or removed without touching the rest of the list."
        ),
    )
    # Repeatable work-experience entries — same free-form-list-of-dicts shape
    # and popup-editor pattern as `qualifications` above.
    experience = models.JSONField(
        default=list,
        blank=True,
        help_text=(
            "List of {id, organization, title, start_year, end_year, is_current, "
            "description} dicts. Repeatable entries edited via the profile page's "
            "popup add/edit UI."
        ),
    )
    professional_background = models.TextField(
        blank=True,
        help_text="A short narrative of the user's professional experience.",
    )
    areas_of_interest = models.CharField(
        max_length=500,
        blank=True,
        help_text="Comma-separated tags. Drives course recommendations and mentor matching.",
    )
    timezone = models.CharField(max_length=64, default="UTC")
    preferred_language = models.CharField(max_length=10, default="en")

    def __str__(self):
        return f"Profile for {self.user_id}"

    @property
    def age_years(self) -> int | None:
        if not self.date_of_birth:
            return None
        today = timezone.now().date()
        years = today.year - self.date_of_birth.year
        # subtract one if birthday hasn't occurred this year yet
        if (today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day):
            years -= 1
        return years

    @property
    def is_youth(self) -> bool | None:
        age = self.age_years
        if age is None:
            return None
        return age <= self.YOUTH_AGE_THRESHOLD


class ProfileVisibility(models.Model):
    """WS5.2 / FRREP-UR012 — per-field profile visibility flags.

    The PRD requires users to control which profile fields are visible to other
    platform users. We model this as a row of booleans rather than a permissions
    table so the typical "everything visible" case is just default field values
    and a missing row implies the most-permissive defaults.

    Privacy-sensitive fields (gender, date_of_birth) are NOT exposed here at
    all — those stay aggregate-only by policy (see UserProfile model docstring
    for the AR010 indicator data path).
    """

    user = models.OneToOneField(
        "CustomUser",
        on_delete=models.CASCADE,
        related_name="visibility",
    )
    show_email = models.BooleanField(default=False, help_text="Display email on the public profile?")
    show_phone = models.BooleanField(default=False)
    show_country = models.BooleanField(default=True)
    show_institution = models.BooleanField(default=True)
    show_degree_level = models.BooleanField(default=True)
    show_qualifications = models.BooleanField(default=True)
    show_experience = models.BooleanField(default=True)
    show_professional_background = models.BooleanField(default=True)
    show_areas_of_interest = models.BooleanField(default=True)
    show_bio = models.BooleanField(default=True)
    appear_in_directory = models.BooleanField(
        default=True,
        help_text="When false, the user is hidden from search and discovery feeds entirely.",
    )

    class Meta:
        verbose_name = "profile visibility settings"
        verbose_name_plural = "profile visibility settings"

    def __str__(self):
        return f"Visibility settings for {self.user_id}"


class MFADevice(models.Model):
    class DeviceType(models.TextChoices):
        TOTP = "totp", "TOTP"

    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name="mfa_devices")
    device_type = models.CharField(max_length=16, choices=DeviceType.choices, default=DeviceType.TOTP)
    name = models.CharField(max_length=64, blank=True)
    secret = models.CharField(max_length=128, blank=True)
    is_verified = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]
