from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models

from apps.core.storage.paths import upload_to_projects
from apps.rims.grants.models import Award


class Project(models.Model):
    class Status(models.TextChoices):
        PLANNED = "planned", "Planned"
        ACTIVE = "active", "Active"
        AT_RISK = "at_risk", "At risk"
        CLOSED = "closed", "Closed"

    award = models.OneToOneField(Award, on_delete=models.CASCADE, related_name="project")
    finance_budget = models.ForeignKey(
        "rims_finance.Budget",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="linked_projects",
        help_text="Formal FM budget this execution project reports against (must be for the same award).",
    )
    title = models.CharField(max_length=255)
    lead = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="led_projects",
        help_text="Principal Investigator / project lead responsible for delivery.",
    )
    summary = models.TextField(blank=True)
    start_date = models.DateField()
    end_date = models.DateField()
    status = models.CharField(max_length=16, choices=Status.choices, default=Status.PLANNED, db_index=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at"]

    def clean(self):
        super().clean()
        if self.finance_budget_id and self.award_id and self.finance_budget.award_id != self.award_id:
            raise ValidationError(
                {"finance_budget": "Selected budget must belong to this project's grant award."}
            )

    def __str__(self):
        return self.title


class ProjectBudgetLine(models.Model):
    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="budget_lines")
    label = models.CharField(max_length=255)
    allocated = models.DecimalField(max_digits=14, decimal_places=2)
    spent = models.DecimalField(max_digits=14, decimal_places=2, default=0)

    class Meta:
        ordering = ["pk"]


class Milestone(models.Model):
    class Status(models.TextChoices):
        PLANNED = "planned", "Planned"
        SUBMITTED = "submitted", "Submitted"
        UNDER_REVIEW = "under_review", "Under review"
        REVISION_REQUIRED = "revision_required", "Revision required"
        ACCEPTED = "accepted", "Accepted"
        OVERDUE = "overdue", "Overdue"

    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="milestones")
    name = models.CharField(max_length=255)
    due_date = models.DateField()
    completion_percent = models.PositiveSmallIntegerField(default=0)
    status = models.CharField(max_length=24, choices=Status.choices, default=Status.PLANNED, db_index=True)
    report_due = models.BooleanField(default=True, help_text="Expects a narrative/financial report")
    report_submitted_at = models.DateTimeField(null=True, blank=True)
    report_summary = models.TextField(blank=True)
    submitted_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="submitted_milestones",
    )
    actual_completion_date = models.DateField(
        null=True, blank=True,
        help_text="Date the milestone was actually completed.",
    )
    progress_note = models.TextField(
        blank=True,
        help_text="Awardee note on progress — what was done, any delays or issues.",
    )
    reviewed_at = models.DateTimeField(null=True, blank=True)
    reviewed_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="reviewed_milestones",
    )
    review_comment = models.TextField(blank=True)

    class Meta:
        ordering = ["due_date"]

    def clean(self):
        super().clean()
        if self.completion_percent < 0 or self.completion_percent > 100:
            raise ValidationError({"completion_percent": "Completion percent must be between 0 and 100."})
        # PRD §5.1 FRFA-AA010 — milestone due_date must fall within the
        # project's active period (inclusive). Only enforced when both the
        # milestone and project dates are set; project bounds derive from the
        # award's project_end_date when the Project.end_date is missing.
        if self.due_date is None or self.project_id is None:
            return
        project = self.project
        start = getattr(project, "start_date", None)
        end = getattr(project, "end_date", None)
        if start and self.due_date < start:
            raise ValidationError(
                {"due_date": f"Milestone due date {self.due_date} falls before project start {start}."}
            )
        if end and self.due_date > end:
            raise ValidationError(
                {"due_date": f"Milestone due date {self.due_date} falls after project end {end}."}
            )


class Deliverable(models.Model):
    milestone = models.ForeignKey(Milestone, on_delete=models.CASCADE, related_name="deliverables")
    title = models.CharField(max_length=255)
    file = models.FileField(upload_to=upload_to_projects, blank=True, null=True)
    notes = models.TextField(blank=True)


class Beneficiary(models.Model):
    """PRD §4.3.4 — direct beneficiaries reached by a project (students,
    farmers, SMEs, household heads, etc.).

    Demographic detail is intentionally a JSON blob: the M&E framework
    needs the data but the schema differs across funder reporting
    requirements. Reports / exports unwrap the JSON to whichever fields
    they need.
    """

    class BeneficiaryRole(models.TextChoices):
        DIRECT = "direct", "Direct beneficiary"
        INDIRECT = "indirect", "Indirect beneficiary"
        STAKEHOLDER = "stakeholder", "Stakeholder"
        PARTNER = "partner", "Partner"

    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="beneficiaries")
    full_name = models.CharField(max_length=255)
    role = models.CharField(
        max_length=24,
        choices=BeneficiaryRole.choices,
        default=BeneficiaryRole.DIRECT,
    )
    contact_email = models.EmailField(blank=True)
    contact_phone = models.CharField(max_length=64, blank=True)
    demographic = models.JSONField(
        default=dict,
        blank=True,
        help_text=(
            'Open-shape demographic data, e.g. {"gender":"F","age":24,"district":"Mukono"}. '
            "Reports / exports unwrap whichever keys they need."
        ),
    )
    joined_on = models.DateField(null=True, blank=True)
    exit_on = models.DateField(null=True, blank=True)
    notes = models.TextField(blank=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="beneficiaries_created",
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

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

    def __str__(self):  # pragma: no cover
        return self.full_name


class BeneficiaryAttendance(models.Model):
    """Per-event attendance row for a Beneficiary (workshops, trainings,
    field days, etc.). Lightweight log so M&E can count touchpoints."""

    beneficiary = models.ForeignKey(Beneficiary, on_delete=models.CASCADE, related_name="attendance")
    event_label = models.CharField(max_length=255)
    occurred_on = models.DateField()
    notes = models.TextField(blank=True)
    recorded_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="beneficiary_attendance_recorded",
    )
    recorded_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-occurred_on", "-pk"]


class MilestoneAudit(models.Model):
    """Lightweight milestone lifecycle history for PRD compliance traceability."""

    milestone = models.ForeignKey(Milestone, on_delete=models.CASCADE, related_name="audit_events")
    event = models.CharField(max_length=64)
    actor = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="milestone_audit_events",
    )
    payload = models.JSONField(default=dict, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]


class RiskFlag(models.Model):
    class Severity(models.TextChoices):
        LOW = "low", "Low"
        MEDIUM = "medium", "Medium"
        HIGH = "high", "High"

    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="risk_flags")
    reason = models.CharField(max_length=255)
    severity = models.CharField(max_length=16, choices=Severity.choices, default=Severity.MEDIUM)
    cleared = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]
