"""View tests for OutputDeliverable CRUD + record-progress (FRMFL014-019)."""
from __future__ import annotations

from datetime import date
from decimal import Decimal

import pytest
from django.contrib.auth import get_user_model
from django.test import Client
from django.urls import reverse

from apps.core.permissions.roles import UserRole
from apps.mel.indicators.models import LogFrame, LogFrameLevel, LogFrameRow
from apps.mel.tracking.models import OutputDeliverable, OutputDeliverableStatus

pytestmark = pytest.mark.django_db

User = get_user_model()


_LF_COUNTER = {"n": 0}


def _next_slug(prefix: str) -> str:
    _LF_COUNTER["n"] += 1
    return f"{prefix}-{_LF_COUNTER['n']}"


def _output_row():
    slug = _next_slug("vlf-out")
    lf = LogFrame.objects.create(name=f"VLF-O {slug}", slug=slug)
    impact = LogFrameRow.objects.create(logframe=lf, level=LogFrameLevel.IMPACT, title="I")
    outcome = LogFrameRow.objects.create(logframe=lf, level=LogFrameLevel.OUTCOME, title="O", parent=impact)
    return LogFrameRow.objects.create(logframe=lf, level=LogFrameLevel.OUTPUT, title="P", parent=outcome)


def _activity_row():
    slug = _next_slug("vlf-act")
    lf = LogFrame.objects.create(name=f"VLF-A {slug}", slug=slug)
    impact = LogFrameRow.objects.create(logframe=lf, level=LogFrameLevel.IMPACT, title="I")
    outcome = LogFrameRow.objects.create(logframe=lf, level=LogFrameLevel.OUTCOME, title="O", parent=impact)
    output = LogFrameRow.objects.create(logframe=lf, level=LogFrameLevel.OUTPUT, title="P", parent=outcome)
    return LogFrameRow.objects.create(logframe=lf, level=LogFrameLevel.ACTIVITY, title="A", parent=output)


def _make_user(email: str, role: str | None) -> User:
    user = User.objects.create_user(email=email, password="x")
    if role is not None:
        user.role = role
        user.save(update_fields=["role"])
    return user


def _officer():
    return _make_user("output-officer@example.test", UserRole.MEL_OFFICER.value)


def _deliverable(target=Decimal("10")):
    return OutputDeliverable.objects.create(
        logframe_row=_output_row(),
        title="Reports batch",
        target_quantity=target,
        unit="reports",
    )


# ---------------------------------------------------------------------------
# List view
# ---------------------------------------------------------------------------


def test_output_list_reachable_to_officer():
    d = _deliverable()
    client = Client()
    client.force_login(_officer())
    resp = client.get(reverse("mel_tracking:output_list"))
    assert resp.status_code == 200
    assert d.title.encode() in resp.content


def test_output_list_blocks_unrelated_role():
    _deliverable()
    client = Client()
    client.force_login(_make_user("learner@example.test", UserRole.LEARNER.value))
    resp = client.get(reverse("mel_tracking:output_list"))
    assert resp.status_code == 403


def test_output_list_filters_by_status():
    d1 = _deliverable()  # status=DRAFT
    d2 = _deliverable()
    d2.title = "Completed deliverable"
    d2.status = OutputDeliverableStatus.COMPLETED
    d2.achieved_quantity = Decimal("10")
    d2.save(update_fields=["title", "status", "achieved_quantity"])

    client = Client()
    client.force_login(_officer())
    resp = client.get(reverse("mel_tracking:output_list") + "?status=completed")
    assert resp.status_code == 200
    assert d2.title.encode() in resp.content
    assert d1.title.encode() not in resp.content


# ---------------------------------------------------------------------------
# Create / update views
# ---------------------------------------------------------------------------


def test_output_create_rejects_non_output_logframe_row():
    activity_row = _activity_row()
    client = Client()
    client.force_login(_officer())
    resp = client.post(
        reverse("mel_tracking:output_create"),
        data={
            "logframe_row": activity_row.pk,
            "title": "Wrong row",
            "target_quantity": "5",
            "achieved_quantity": "0",
            "unit": "things",
        },
    )
    assert resp.status_code == 200  # form re-render with error
    assert OutputDeliverable.objects.filter(title="Wrong row").exists() is False


def test_output_create_persists_and_redirects():
    row = _output_row()
    client = Client()
    client.force_login(_officer())
    resp = client.post(
        reverse("mel_tracking:output_create"),
        data={
            "logframe_row": row.pk,
            "title": "Quarterly reports",
            "target_quantity": "4",
            "achieved_quantity": "0",
            "unit": "reports",
        },
    )
    deliverable = OutputDeliverable.objects.get(title="Quarterly reports")
    assert resp.status_code == 302
    assert resp["Location"] == reverse(
        "mel_tracking:output_detail", kwargs={"pk": deliverable.pk}
    )


# ---------------------------------------------------------------------------
# Detail view
# ---------------------------------------------------------------------------


def test_output_detail_renders_progress_form():
    d = _deliverable()
    client = Client()
    client.force_login(_officer())
    resp = client.get(reverse("mel_tracking:output_detail", kwargs={"pk": d.pk}))
    assert resp.status_code == 200
    assert b"Record progress" in resp.content


# ---------------------------------------------------------------------------
# Record progress view
# ---------------------------------------------------------------------------


def test_record_progress_marks_in_progress_and_completed():
    d = _deliverable(target=Decimal("10"))
    client = Client()
    client.force_login(_officer())

    # Halfway → IN_PROGRESS (auto-derived in service).
    resp = client.post(
        reverse("mel_tracking:output_record_progress", kwargs={"pk": d.pk}),
        data={"achieved": "5"},
    )
    assert resp.status_code == 302
    d.refresh_from_db()
    assert d.status == OutputDeliverableStatus.IN_PROGRESS
    assert d.percent_achieved == pytest.approx(50.0)

    # Hit target → COMPLETED.
    client.post(
        reverse("mel_tracking:output_record_progress", kwargs={"pk": d.pk}),
        data={"achieved": "10"},
    )
    d.refresh_from_db()
    assert d.status == OutputDeliverableStatus.COMPLETED


def test_record_progress_marks_overdue_when_past_due_date():
    d = OutputDeliverable.objects.create(
        logframe_row=_output_row(),
        title="Overdue deliverable",
        target_quantity=Decimal("10"),
        unit="reports",
        due_date=date(2020, 1, 1),
    )
    client = Client()
    client.force_login(_officer())
    client.post(
        reverse("mel_tracking:output_record_progress", kwargs={"pk": d.pk}),
        data={"achieved": "2"},
    )
    d.refresh_from_db()
    assert d.status == OutputDeliverableStatus.OVERDUE
