from django.conf import settings
from django.db import models

from apps.core.authentication.models import Institution
from apps.core.storage import FileValidator
from apps.core.storage.paths import upload_to_operations

_operations_doc_validator = FileValidator(
    module="rims",
    allowed_categories=["document", "data", "archive"],
)


class Partner(models.Model):
    """External partner organisation (funder, agency, etc.)."""

    class PartnerType(models.TextChoices):
        FUNDER = "funder", "Funder / Donor"
        ACADEMIC = "academic", "Academic Institution"
        GOVERNMENT = "government", "Government Agency"
        NGO = "ngo", "NGO / Development Organisation"
        PRIVATE = "private", "Private Sector"
        RESEARCH = "research", "Research Organisation"
        OTHER = "other", "Other"

    name = models.CharField(max_length=255, db_index=True)
    partner_type = models.CharField(
        max_length=16,
        choices=PartnerType.choices,
        blank=True,
    )
    country = models.CharField(max_length=100, blank=True, help_text="Full country name, optional")
    contact_email = models.EmailField(blank=True)
    website = models.URLField(blank=True)
    logo = models.FileField(upload_to=upload_to_operations, blank=True, null=True)
    notes = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["name"]

    def __str__(self):
        return self.name


class MOU(models.Model):
    class Status(models.TextChoices):
        DRAFT = "draft", "Draft"
        ACTIVE = "active", "Active"
        EXPIRED = "expired", "Expired"
        SUPERSEDED = "superseded", "Superseded"

    partner = models.ForeignKey(Partner, on_delete=models.CASCADE, related_name="mous")
    institution = models.ForeignKey(
        Institution,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="mous",
    )
    title = models.CharField(max_length=255)
    status = models.CharField(max_length=16, choices=Status.choices, default=Status.DRAFT)
    signed_on = models.DateField(null=True, blank=True)
    expires_on = models.DateField(null=True, blank=True)
    document = models.FileField(
        upload_to=upload_to_operations,
        blank=True,
        null=True,
        validators=[_operations_doc_validator],
    )
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="mous_created",
    )
    created_at = models.DateTimeField(auto_now_add=True)
    # P1 (assessment §3 #10) — dedup hint for the 30-day renewal reminder beat
    # so a long-running ACTIVE MOU doesn't spam the relationship owner daily.
    last_renewal_reminder_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ["-created_at"]
        verbose_name_plural = "MOUs"

    def __str__(self):
        return self.title

    @property
    def is_expiring_soon(self) -> bool:
        """True when status is ACTIVE and expires within 30 days. Used by
        templates to surface the renewal banner without inline date math."""
        from datetime import timedelta

        from django.utils import timezone

        if self.status != self.Status.ACTIVE or not self.expires_on:
            return False
        today = timezone.now().date()
        return today <= self.expires_on <= today + timedelta(days=30)


class Donor(models.Model):
    """PRD §5.1 FRFA-GPS007–009 — external donor / funder profile.

    Kept separate from :class:`Partner` so donor-specific fields (tax ID,
    focal-area restrictions, default reporting cadence) can grow without
    polluting the broader partner taxonomy. Duplicates are prevented by a
    case-insensitive (name, country) uniqueness check enforced via
    :class:`DonorForm.clean` — the DB-level uniqueness constraint allows
    multiple rows per country-less donor for historical data.
    """

    class DonorType(models.TextChoices):
        BILATERAL = "bilateral", "Bilateral agency"
        MULTILATERAL = "multilateral", "Multilateral agency"
        FOUNDATION = "foundation", "Foundation"
        CORPORATE = "corporate", "Corporate sponsor"
        GOVERNMENT = "government", "Government body"
        INDIVIDUAL = "individual", "Individual donor"
        OTHER = "other", "Other"

    name = models.CharField(max_length=255, db_index=True)
    donor_type = models.CharField(max_length=16, choices=DonorType.choices, blank=True)
    country = models.CharField(max_length=100, blank=True, help_text="Full country name, optional")
    contact_email = models.EmailField(blank=True)
    contact_phone = models.CharField(max_length=64, blank=True)
    website = models.URLField(blank=True)
    partner = models.ForeignKey(
        Partner,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="donor_profiles",
        help_text="Optional link to the Partner taxonomy when this donor is also tracked there.",
    )
    notes = models.TextField(blank=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="donors_created",
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["name"]
        constraints = [
            models.UniqueConstraint(
                fields=["name", "country"],
                name="uniq_donor_name_country",
            ),
        ]

    def __str__(self):
        return self.name


class ReminderConfig(models.Model):
    """PRD §5.1 FRFA-AA014, FRFA-AA009, FRFA-CO011 — operations-controlled
    cadence for the chase / reminder Celery tasks. Singleton by design: the
    application reads ``ReminderConfig.objects.first()`` (or the default fall-
    back values) so admins do not have to choose the right row.
    """

    milestone_warn_days_before = models.PositiveSmallIntegerField(
        default=14,
        help_text="Days before a milestone due_date to send the awardee a reminder.",
    )
    agreement_chase_grace_days = models.PositiveSmallIntegerField(
        default=14,
        help_text="Days to wait after award issue before chasing missing signed agreements.",
    )
    agreement_chase_max_reminders = models.PositiveSmallIntegerField(
        default=3,
        help_text="Maximum agreement-chase reminder emails per award.",
    )
    closeout_warn_days_before = models.PositiveSmallIntegerField(
        default=7,
        help_text="Days after close-out start before nagging for missing final reports.",
    )
    award_letter_grace_days = models.PositiveSmallIntegerField(
        default=7,
        help_text="Days to wait after award before chasing acknowledgement of the award letter.",
    )
    updated_at = models.DateTimeField(auto_now=True)
    updated_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    class Meta:
        verbose_name = "Reminder configuration"
        verbose_name_plural = "Reminder configuration"

    def __str__(self):  # pragma: no cover - admin display
        return f"Reminder config (updated {self.updated_at:%Y-%m-%d %H:%M})"

    @classmethod
    def get_solo(cls) -> "ReminderConfig":
        obj = cls.objects.first()
        if obj is None:
            obj = cls.objects.create()
        return obj


class RetentionPolicy(models.Model):
    """PRD §5.1 FRFA-CO024 — per-funding-type archive retention rules.

    The grant close-out workflow consults this row when archiving an award to
    decide how long the document repository keeps the materials and after how
    long the bundle is moved to cold storage.
    """

    class FundingType(models.TextChoices):
        GRANT = "grant", "Grant"
        SCHOLARSHIP = "scholarship", "Scholarship"
        FELLOWSHIP = "fellowship", "Fellowship"
        CHALLENGE = "challenge", "Challenge"

    funding_type = models.CharField(
        max_length=16,
        choices=FundingType.choices,
        unique=True,
    )
    retention_years = models.PositiveSmallIntegerField(
        default=7,
        help_text="Years the archive must be kept before deletion is permitted.",
    )
    archive_after_days = models.PositiveSmallIntegerField(
        default=30,
        help_text="Days after award closure before the bundle is moved to archive storage.",
    )
    description = models.TextField(blank=True)
    updated_at = models.DateTimeField(auto_now=True)
    updated_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    class Meta:
        ordering = ["funding_type"]
        verbose_name_plural = "Retention policies"

    def __str__(self):  # pragma: no cover - admin display
        return f"{self.get_funding_type_display()}: keep {self.retention_years}y"


class ReviewerProfile(models.Model):
    """PRD §5.1 FRFA-AM018 — workload + availability data layered onto a user
    so the auto-assignment service can balance load and respect time-off.

    The user-row holds identity / role / org affiliation. The reviewer profile
    holds the *operational* data the auto-assignment cares about: how many
    concurrent reviews this person is willing to take, whether they are
    currently available, and any expertise tags Programme Officers can filter
    on when manually assigning.
    """

    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="reviewer_profile",
    )
    available = models.BooleanField(
        default=True,
        help_text="When False, auto-assignment will skip this reviewer.",
    )
    max_concurrent_reviews = models.PositiveSmallIntegerField(
        default=5,
        help_text="Hard cap on simultaneously open reviews assigned to this person.",
    )
    expertise_tags = models.JSONField(
        default=list,
        blank=True,
        help_text='List of free-text expertise tags, e.g. ["climate","gender","SME"].',
    )
    notes = models.TextField(blank=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["user__email"]

    def __str__(self):  # pragma: no cover - admin display
        return f"Reviewer: {self.user.email}"


class VarianceAlertConfig(models.Model):
    """PRD §5.4 NFRFM004 — operations-controlled threshold for the variance
    alert task. Singleton by design.
    """

    enabled = models.BooleanField(default=True)
    percent_threshold = models.DecimalField(
        max_digits=5,
        decimal_places=2,
        default=20,
        help_text=(
            "Alert when the spent-vs-elapsed-time ratio differs from 1.0 by at "
            "least this many percent."
        ),
    )
    period_window_days = models.PositiveSmallIntegerField(
        default=30,
        help_text="How many days back the alert task evaluates.",
    )
    updated_at = models.DateTimeField(auto_now=True)
    updated_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    class Meta:
        verbose_name = "Variance alert configuration"
        verbose_name_plural = "Variance alert configuration"

    def __str__(self):  # pragma: no cover
        return f"Variance alert config ({'enabled' if self.enabled else 'disabled'})"

    @classmethod
    def get_solo(cls) -> "VarianceAlertConfig":
        obj = cls.objects.first()
        if obj is None:
            obj = cls.objects.create()
        return obj


class Member(models.Model):
    """Links a member institution to a partner (network membership row)."""

    institution = models.ForeignKey(Institution, on_delete=models.CASCADE, related_name="partner_links")
    partner = models.ForeignKey(Partner, on_delete=models.CASCADE, related_name="member_institutions")
    joined_on = models.DateField(auto_now_add=True)
    notes = models.CharField(max_length=255, blank=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=["institution", "partner"], name="uniq_member_institution_partner"),
        ]
        ordering = ["-joined_on"]

    def __str__(self):
        return f"{self.institution} ↔ {self.partner}"


class MemberUniversity(models.Model):
    """Extended profile for RUFORUM member universities (one per Institution)."""

    class MembershipStatus(models.TextChoices):
        ACTIVE = "active", "Active member"
        PROBATIONARY = "probationary", "Probationary"
        SUSPENDED = "suspended", "Suspended"
        FORMER = "former", "Former member"

    institution = models.OneToOneField(
        Institution,
        on_delete=models.CASCADE,
        related_name="member_university_profile",
        limit_choices_to={"is_member": True},
    )
    membership_status = models.CharField(
        max_length=16, choices=MembershipStatus.choices, default=MembershipStatus.ACTIVE
    )
    date_admitted = models.DateField(null=True, blank=True)
    membership_number = models.CharField(max_length=32, blank=True, unique=True)
    vice_chancellor = models.CharField(max_length=200, blank=True)
    focal_person_name = models.CharField(max_length=200, blank=True)
    focal_person_email = models.EmailField(blank=True)
    focal_person_phone = models.CharField(max_length=32, blank=True)
    postgraduate_programmes = models.TextField(blank=True, help_text="List of postgraduate programmes offered.")
    research_focus_areas = models.TextField(blank=True)
    annual_fee_paid = models.BooleanField(default=False)
    annual_fee_year = models.PositiveSmallIntegerField(null=True, blank=True)
    notes = models.TextField(blank=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["institution__name"]
        verbose_name = "Member university"
        verbose_name_plural = "Member universities"

    def __str__(self):
        return f"{self.institution} ({self.get_membership_status_display()})"
