"""HTMX partial tests for projects: budget_progress, milestone_row, risk_flag_row.

Verifies:
- BudgetProgressPartialView renders correct totals from project.budget_lines.
- MilestoneReviewView returns HX-Trigger: milestoneAccepted on accept.
- milestone_row.html renders all status badge variants.
- risk_flag_row.html renders severity badges.
"""

from __future__ import annotations

import uuid
from datetime import timedelta
from decimal import Decimal

import pytest
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils import timezone

from apps.core.permissions.roles import UserRole
from apps.rims.grants.models import Application, GrantCall
from apps.rims.grants.services import (
    award_application,
    shortlist_application,
    submit_application,
)
from apps.rims.projects.models import Milestone, Project, ProjectBudgetLine, RiskFlag


def _awarded_application(applicant_user, institution):
    call = GrantCall.objects.create(
        title="HTMX partial flow call",
        slug=f"htmx-flow-{uuid.uuid4().hex[:10]}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=30),
        status=GrantCall.Status.PUBLISHED,
    )
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    shortlist_application(app)
    award_application(
        app,
        Decimal("10000"),
        timezone.now().date() + timedelta(days=180),
        narrative="",
    )
    return Application.objects.get(pk=app.pk)


@pytest.fixture
def project(db, applicant_user, institution):
    app = _awarded_application(applicant_user, institution)
    p = Project.objects.create(
        award=app.award,
        title="Partial test project",
        summary="",
        start_date=timezone.now().date(),
        end_date=timezone.now().date() + timedelta(days=180),
        status=Project.Status.ACTIVE,
    )
    ProjectBudgetLine.objects.create(project=p, label="Personnel", allocated=Decimal("5000"), spent=Decimal("2500"))
    ProjectBudgetLine.objects.create(project=p, label="Travel", allocated=Decimal("2000"), spent=Decimal("500"))
    return p


@pytest.mark.django_db
def test_budget_progress_partial_renders_totals(client, grants_manager_user, project):
    client.force_login(grants_manager_user)
    resp = client.get(reverse("rims_projects:budget_progress", kwargs={"pk": project.pk}))
    assert resp.status_code == 200
    body = resp.content.decode()
    # Allocated 7000, Spent 3000, Remaining 4000 — humanize adds commas.
    assert "7,000" in body
    assert "3,000" in body
    assert "4,000" in body
    assert "Project budget progress" in body


@pytest.mark.django_db
def test_budget_progress_partial_overspend_flag(client, grants_manager_user, project):
    # Push every line over allocation so the project total spent > allocated.
    for line in project.budget_lines.all():
        line.spent = line.allocated + Decimal("1000")
        line.save(update_fields=["spent"])
    client.force_login(grants_manager_user)
    resp = client.get(reverse("rims_projects:budget_progress", kwargs={"pk": project.pk}))
    assert resp.status_code == 200
    assert b"Overspend" in resp.content


@pytest.mark.django_db
def test_milestone_review_accept_returns_hx_trigger(client, applicant_user, institution, project):
    milestone = Milestone.objects.create(
        project=project,
        name="M1",
        due_date=timezone.now().date() + timedelta(days=7),
        report_due=True,
    )
    User = type(applicant_user)
    director = User.objects.create_user(
        email=f"pd-{uuid.uuid4().hex[:6]}@x.com", password="x"
    )
    director.role = UserRole.PROGRAM_DIRECTOR
    director.save(update_fields=["role"])
    client.force_login(director)
    resp = client.post(
        reverse(
            "rims_projects:milestone_review",
            kwargs={"project_pk": project.pk, "milestone_pk": milestone.pk},
        ),
        {"decision": "accepted", "comment": "ok"},
    )
    assert resp.status_code == 302
    assert resp.headers.get("HX-Trigger") == "milestoneAccepted"


@pytest.mark.django_db
def test_milestone_review_revision_does_not_emit_accept_trigger(
    client, applicant_user, institution, project
):
    milestone = Milestone.objects.create(
        project=project,
        name="M2",
        due_date=timezone.now().date() + timedelta(days=7),
        report_due=True,
    )
    User = type(applicant_user)
    director = User.objects.create_user(
        email=f"pd-{uuid.uuid4().hex[:6]}@x.com", password="x"
    )
    director.role = UserRole.PROGRAM_DIRECTOR
    director.save(update_fields=["role"])
    client.force_login(director)
    resp = client.post(
        reverse(
            "rims_projects:milestone_review",
            kwargs={"project_pk": project.pk, "milestone_pk": milestone.pk},
        ),
        {"decision": "revision_required", "comment": "needs work"},
    )
    assert resp.status_code == 302
    assert resp.headers.get("HX-Trigger") is None


@pytest.mark.django_db
def test_milestone_row_renders_each_status_badge(project):
    statuses_to_labels = [
        (Milestone.Status.PLANNED, "Planned"),
        (Milestone.Status.SUBMITTED, "Submitted"),
        (Milestone.Status.UNDER_REVIEW, "Under review"),
        (Milestone.Status.REVISION_REQUIRED, "Revision required"),
        (Milestone.Status.ACCEPTED, "Accepted"),
        (Milestone.Status.OVERDUE, "Overdue"),
    ]
    for i, (status, label) in enumerate(statuses_to_labels):
        m = Milestone.objects.create(
            project=project,
            name=f"M-{i}",
            due_date=timezone.now().date() + timedelta(days=i + 1),
            status=status,
            report_due=True,
        )
        out = render_to_string(
            "projects/partials/milestone_row.html",
            {
                "m": m,
                "project": project,
                "can_capture_milestones": False,
                "can_review_milestones": False,
            },
        )
        assert label in out


@pytest.mark.django_db
def test_risk_flag_row_renders_severity_badges(project):
    for sev, label in [("low", "Low"), ("medium", "Medium"), ("high", "High")]:
        flag = RiskFlag.objects.create(project=project, reason=f"{sev}-risk", severity=sev)
        out = render_to_string("projects/partials/risk_flag_row.html", {"flag": flag})
        assert label in out
        assert f"{sev}-risk" in out
