"""PRD §5.4 FRFM036/037/038 — segregation-of-duties telemetry."""
from __future__ import annotations

from datetime import timedelta
from decimal import Decimal
from unittest.mock import patch

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

from apps.core.audit.models import AuditLog
from apps.core.audit.tasks import persist_audit_log
from apps.core.permissions.roles import UserRole
from apps.rims.finance.models import Budget, BudgetLine, FinanceApproval
from apps.rims.finance.services import (
    approve_budget_line,
    approve_budget_total,
    list_approval_history,
)
from apps.rims.grants.models import Application, Award, GrantCall

User = get_user_model()


@pytest.fixture(autouse=True)
def _sync_audit():
    with patch("apps.core.audit.tasks.write_audit_log.delay", side_effect=lambda **kw: persist_audit_log(**kw)):
        with patch(
            "apps.core.audit.mixins.transaction.on_commit",
            side_effect=lambda fn: fn(),
        ):
            yield


@pytest.fixture
def finance_manager_user(db, institution):
    u = User.objects.create_user(email="fm.tel@example.com", password="pw-6c")
    u.role = UserRole.FINANCE_MANAGER
    u.save()
    return u


@pytest.fixture
def program_coordinator_user(db, institution):
    u = User.objects.create_user(email="pc.tel@example.com", password="pw-6c")
    u.role = UserRole.PROGRAM_COORDINATOR
    u.save()
    return u


def _budget(applicant_user, institution) -> Budget:
    call = GrantCall.objects.create(
        title=f"6C-tel call {timezone.now().timestamp()}",
        slug=f"6c-tel-{int(timezone.now().timestamp() * 1000) % 10_000_000}",
        call_type="grant",
        opens_at=timezone.now() - timedelta(days=200),
        closes_at=timezone.now() + timedelta(days=10),
        status=GrantCall.Status.PUBLISHED,
    )
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    award = Award.objects.create(
        application=app,
        amount=Decimal("20000"),
        currency="USD",
        awarded_at=timezone.now(),
        project_end_date=timezone.now().date() + timedelta(days=365),
    )
    budget = Budget.objects.create(award=award, name="Telemetry budget", currency="USD")
    BudgetLine.objects.create(budget=budget, label="Line A", amount=Decimal("12000"))
    BudgetLine.objects.create(budget=budget, label="Line B", amount=Decimal("8000"))
    return budget


@pytest.mark.django_db
def test_approve_budget_total_records_telemetry_and_audit(
    applicant_user, institution, finance_manager_user
):
    budget = _budget(applicant_user, institution)
    record = approve_budget_total(budget, actor=finance_manager_user, tier="finance_manager")
    assert record.tier == "finance_manager"
    assert record.decision == FinanceApproval.Decision.APPROVED
    history = list(list_approval_history(budget))
    assert len(history) == 1
    assert AuditLog.objects.filter(action="BUDGET_APPROVED_TOTAL").exists()


@pytest.mark.django_db
def test_approve_budget_line_records_telemetry_and_audit(
    applicant_user, institution, finance_manager_user
):
    budget = _budget(applicant_user, institution)
    line = budget.lines.first()
    record = approve_budget_line(line, actor=finance_manager_user, tier="finance_manager")
    assert record.target_object_id == line.pk
    assert AuditLog.objects.filter(action="BUDGET_APPROVED_LINE").exists()


@pytest.mark.django_db
def test_telemetry_returns_history_newest_first(
    applicant_user, institution, finance_manager_user, program_coordinator_user
):
    budget = _budget(applicant_user, institution)
    approve_budget_total(budget, actor=finance_manager_user, tier="finance_manager")
    approve_budget_total(budget, actor=program_coordinator_user, tier="program_coordinator")
    history = list(list_approval_history(budget))
    assert len(history) == 2
    assert history[0].actor == program_coordinator_user
