"""G4 — Programme-level health score."""
from __future__ import annotations

from datetime import date, timedelta
from decimal import Decimal

import pytest
from django.contrib.auth import get_user_model
from django.utils import timezone

from apps.core.permissions.roles import UserRole
from apps.mel.indicators.models import (
    DataPoint,
    Indicator,
    IndicatorFrequency,
    IndicatorTarget,
    LogFrame,
    LogFrameLevel,
    LogFrameRow,
    ProgrammeHealthScore,
)
from apps.mel.indicators.services import (
    compute_programme_health,
    record_data_point,
)

User = get_user_model()
pytestmark = pytest.mark.django_db


@pytest.fixture
def officer():
    return User.objects.create_user(
        email="g4@example.com", password="x", role=UserRole.MEL_OFFICER,
    )


@pytest.fixture
def logframe(officer):
    return LogFrame.objects.create(
        name="G4 Programme", slug="g4-prog", owner=officer,
        period_start=date(2026, 1, 1), period_end=date(2026, 12, 31),
    )


def _build_indicator_with_target(logframe, code: str, target_value: Decimal, period_label: str):
    impact = LogFrameRow.objects.create(logframe=logframe, level=LogFrameLevel.IMPACT, title=f"I-{code}")
    outcome = LogFrameRow.objects.create(logframe=logframe, level=LogFrameLevel.OUTCOME, title="O", parent=impact)
    output = LogFrameRow.objects.create(logframe=logframe, level=LogFrameLevel.OUTPUT, title="P", parent=outcome)
    ind = Indicator.objects.create(
        logframe_row=output, code=code, name=code,
        unit="count", calculation_method="count", data_source="manual",
        frequency=IndicatorFrequency.MONTHLY,
    )
    IndicatorTarget.objects.create(
        indicator=ind,
        period_label=period_label,
        period_start=date(2026, 1, 1),
        period_end=date(2026, 1, 31),
        target_value=target_value,
        baseline_value=Decimal("0"),
    )
    return ind


def test_compute_programme_health_with_only_rag_term(officer, logframe):
    """Smoke: a logframe with one GREEN indicator and no other terms scores 100."""
    period = "2026-01"
    ind = _build_indicator_with_target(logframe, "g4-1", Decimal("100"), period)
    record_data_point(
        indicator=ind, value=100, period_label=period,
        reported_by=officer, source_module="test", source_object_id="g4-1",
        auto_verify=True,
    )
    score = compute_programme_health(logframe, period)
    assert score.indicator_count == 1
    assert score.rag_health is not None
    # Without any activities/feedback, RAG carries the full weight (1.0).
    assert score.score == Decimal("100.00")


def test_compute_programme_health_idempotent_upsert(officer, logframe):
    period = "2026-01"
    ind = _build_indicator_with_target(logframe, "g4-2", Decimal("100"), period)
    record_data_point(
        indicator=ind, value=50, period_label=period,
        reported_by=officer, source_module="test", source_object_id="g4-2",
        auto_verify=True,
    )
    first = compute_programme_health(logframe, period)
    second = compute_programme_health(logframe, period)
    assert ProgrammeHealthScore.objects.count() == 1
    assert first.pk == second.pk


def test_weight_redistribution_when_terms_missing(officer, logframe):
    """When only RAG is computable, its weight scales to 1.0 (40% → 100%)."""
    period = "2026-02"
    ind = _build_indicator_with_target(logframe, "g4-3", Decimal("100"), period)
    record_data_point(
        indicator=ind, value=80, period_label=period,
        reported_by=officer, source_module="test", source_object_id="g4-3a",
        auto_verify=True,
    )
    score = compute_programme_health(logframe, period)
    weights = score.weights_applied
    assert "rag_health" in weights
    assert weights["rag_health"] == pytest.approx(1.0)
    # No activity / budget / feedback terms present.
    assert "activity_on_time" not in weights
    assert score.activity_on_time is None


def test_no_active_indicators_yields_zero(officer, logframe):
    score = compute_programme_health(logframe, "2026-03")
    assert score.score == Decimal("0.00")
    assert score.indicator_count == 0
