"""Department finance rollups: attribution paths, co-ownership, year filter."""
from datetime import date
from decimal import Decimal

import pytest
from django.contrib.auth import get_user_model
from rest_framework.test import APIClient

from apps.companies.models import Department, Organization, OrganizationMembership
from apps.projects.models import Project, ProjectFinance, ProjectFund


@pytest.fixture
def organization(db):
    return Organization.objects.create(name="Dept Finance Org")


@pytest.fixture
def api_client(db, organization):
    User = get_user_model()
    admin = User.objects.create_superuser(email="deptfin-admin@test.local", password="x")
    auto_org = admin.organizations.first()
    OrganizationMembership.objects.filter(organization=auto_org, user=admin).update(
        organization=organization
    )
    auto_org.delete()
    c = APIClient()
    c.force_authenticate(admin)
    return c


@pytest.fixture
def eco(organization):
    return Department.objects.create(
        organization=organization, name="Economics", code="ECO", is_research=True
    )


@pytest.fixture
def pol(organization):
    return Department.objects.create(
        organization=organization, name="Politics", code="POL", is_research=True
    )


def make_project(organization, name, departments=()):
    p = Project.objects.create(organization=organization, name=name)
    if departments:
        p.departments.set(departments)
    p.assign_project_number()
    return p


@pytest.mark.django_db
class TestDepartmentFinanceList:
    def test_project_finance_attributes_to_department(self, api_client, organization, eco):
        p = make_project(organization, "Trade Study", [eco])
        ProjectFinance.objects.create(
            project=p, description="grant in", amount=Decimal("1000"),
            amount_idr=Decimal("1000"), currency="IDR", type="income", date=date(2026, 3, 1),
        )
        ProjectFinance.objects.create(
            project=p, description="fieldwork", amount=Decimal("400"),
            amount_idr=Decimal("400"), currency="IDR", type="expense", date=date(2026, 3, 5),
        )

        resp = api_client.get("/api/department-finance/")
        assert resp.status_code == 200, resp.content
        row = next(d for d in resp.data["departments"] if d["code"] == "ECO")
        assert Decimal(row["income"]) == Decimal("1000")
        assert Decimal(row["expense"]) == Decimal("400")
        assert Decimal(row["net"]) == Decimal("600")
        assert row["finance_entry_count"] == 2
        assert row["project_count"] == 1

    def test_co_owned_project_credited_in_full_to_each(self, api_client, organization, eco, pol):
        p = make_project(organization, "Joint", [eco, pol])
        ProjectFinance.objects.create(
            project=p, description="shared spend", amount=Decimal("900"),
            amount_idr=Decimal("900"), currency="IDR", type="expense", date=date(2026, 4, 1),
        )
        resp = api_client.get("/api/department-finance/")
        assert resp.status_code == 200, resp.content
        by_code = {d["code"]: d for d in resp.data["departments"]}
        # Full amount to each owner — not split.
        assert Decimal(by_code["ECO"]["expense"]) == Decimal("900")
        assert Decimal(by_code["POL"]["expense"]) == Decimal("900")
        assert resp.data["co_owned_project_count"] == 1

    def test_department_without_projects_reports_zeroes(self, api_client, organization, eco):
        resp = api_client.get("/api/department-finance/")
        assert resp.status_code == 200, resp.content
        row = next(d for d in resp.data["departments"] if d["code"] == "ECO")
        assert Decimal(row["income"]) == Decimal("0")
        assert row["project_count"] == 0

    def test_fund_pledged_attributes_via_project(self, api_client, organization, eco):
        p = make_project(organization, "Funded", [eco])
        ProjectFund.objects.create(
            organization=organization, project=p, source="Donor A",
            amount=Decimal("5000"), amount_idr=Decimal("5000"), currency="IDR",
            status="pledged", agreement_date=date(2026, 2, 1),
        )
        resp = api_client.get("/api/department-finance/")
        row = next(d for d in resp.data["departments"] if d["code"] == "ECO")
        assert Decimal(row["fund_pledged"]) == Decimal("5000")
        assert row["fund_count"] == 1

    def test_year_filter_excludes_other_years(self, api_client, organization, eco):
        p = make_project(organization, "Multi-year", [eco])
        for y, amt in ((2025, "100"), (2026, "700")):
            ProjectFinance.objects.create(
                project=p, description=f"spend {y}", amount=Decimal(amt),
                amount_idr=Decimal(amt), currency="IDR", type="expense", date=date(y, 6, 1),
            )
        resp = api_client.get("/api/department-finance/?year=2026")
        row = next(d for d in resp.data["departments"] if d["code"] == "ECO")
        assert Decimal(row["expense"]) == Decimal("700")
        assert resp.data["year"] == 2026

    def test_other_org_data_is_not_visible(self, api_client, organization, eco):
        other_org = Organization.objects.create(name="Other Org")
        other_dept = Department.objects.create(
            organization=other_org, name="Foreign", code="FGN", is_research=True
        )
        p = make_project(other_org, "Not Mine", [other_dept])
        ProjectFinance.objects.create(
            project=p, description="theirs", amount=Decimal("999"),
            amount_idr=Decimal("999"), currency="IDR", type="expense", date=date(2026, 5, 1),
        )
        resp = api_client.get("/api/department-finance/")
        codes = {d["code"] for d in resp.data["departments"]}
        assert "FGN" not in codes


@pytest.mark.django_db
class TestDepartmentFinanceDetail:
    def test_trend_buckets_monthly_and_yearly(self, api_client, organization, eco):
        p = make_project(organization, "Trended", [eco])
        for d, amt in ((date(2026, 1, 10), "100"), (date(2026, 2, 10), "250")):
            ProjectFinance.objects.create(
                project=p, description="spend", amount=Decimal(amt),
                amount_idr=Decimal(amt), currency="IDR", type="expense", date=d,
            )

        monthly = api_client.get(f"/api/department-finance/{eco.id}/trend/?granularity=monthly")
        assert monthly.status_code == 200, monthly.content
        periods = [pt["period"] for pt in monthly.data["series"]]
        assert periods == ["2026-01", "2026-02"]

        yearly = api_client.get(f"/api/department-finance/{eco.id}/trend/?granularity=yearly")
        assert [pt["period"] for pt in yearly.data["series"]] == ["2026"]
        assert Decimal(yearly.data["series"][0]["expense"]) == Decimal("350")

    def test_transactions_merges_sources_newest_first(self, api_client, organization, eco):
        p = make_project(organization, "Mixed", [eco])
        ProjectFinance.objects.create(
            project=p, description="older expense", amount=Decimal("10"),
            amount_idr=Decimal("10"), currency="IDR", type="expense", date=date(2026, 1, 1),
        )
        ProjectFund.objects.create(
            organization=organization, project=p, source="Donor B",
            amount=Decimal("20"), amount_idr=Decimal("20"), currency="IDR",
            status="pledged", agreement_date=date(2026, 9, 1),
        )
        resp = api_client.get(f"/api/department-finance/{eco.id}/transactions/")
        assert resp.status_code == 200, resp.content
        assert resp.data["count"] == 2
        sources = [t["source"] for t in resp.data["results"]]
        # Newest first: the September fund precedes the January entry.
        assert sources == ["project_fund", "project_finance"]
        assert resp.data["results"][0]["direction"] == "income"

    def test_transactions_carry_project_attribution(self, api_client, organization, eco):
        p = make_project(organization, "Named Project", [eco])
        ProjectFinance.objects.create(
            project=p, description="x", amount=Decimal("5"), amount_idr=Decimal("5"),
            currency="IDR", type="expense", date=date(2026, 7, 1),
        )
        resp = api_client.get(f"/api/department-finance/{eco.id}/transactions/")
        row = resp.data["results"][0]
        assert row["project_name"] == "Named Project"
        # p.id is a str of hex digits with no dashes; the API returns the
        # canonical dashed UUID, so compare on the hex form.
        assert row["project_id"].replace("-", "") == str(p.id).replace("-", "")

    def test_projects_breakdown_sorted_by_spend(self, api_client, organization, eco):
        small = make_project(organization, "Small", [eco])
        big = make_project(organization, "Big", [eco])
        ProjectFinance.objects.create(
            project=small, description="a", amount=Decimal("10"), amount_idr=Decimal("10"),
            currency="IDR", type="expense", date=date(2026, 3, 1),
        )
        ProjectFinance.objects.create(
            project=big, description="b", amount=Decimal("900"), amount_idr=Decimal("900"),
            currency="IDR", type="expense", date=date(2026, 3, 1),
        )
        resp = api_client.get(f"/api/department-finance/{eco.id}/projects/")
        assert resp.status_code == 200, resp.content
        assert [r["name"] for r in resp.data["results"]] == ["Big", "Small"]
        assert resp.data["results"][0]["project_number"]

    def test_unknown_department_404s(self, api_client, organization):
        missing = "00000000-0000-0000-0000-0000000000ff"
        for suffix in ("trend", "transactions", "projects"):
            resp = api_client.get(f"/api/department-finance/{missing}/{suffix}/")
            assert resp.status_code == 404, (suffix, resp.status_code)

    def test_other_org_department_is_not_reachable(self, api_client):
        other_org = Organization.objects.create(name="Other Org 2")
        foreign = Department.objects.create(
            organization=other_org, name="Foreign2", code="FG2", is_research=True
        )
        resp = api_client.get(f"/api/department-finance/{foreign.id}/transactions/")
        assert resp.status_code == 404
