"""Unit tests: model __str__, serializer computed fields, view helpers.

These exercise pure logic in isolation (no API client), complementing the
endpoint-level tests in test_accounting.py and test_integration.py.
"""

import datetime
from decimal import Decimal

import pytest

from apps.accounting.models import (
    Account, Fund, Transaction, JournalEntry, JournalLine,
)
from apps.accounting.serializers import FundSerializer, JournalEntrySerializer
from apps.accounting import views
from apps.companies.models import Organization


@pytest.fixture
def org(db):
    return Organization.objects.create(name="Test Org")


@pytest.mark.django_db
class TestModelStr:
    def test_account_str(self):
        a = Account(code="1000", name="Cash and Bank", type="Asset", normal_balance="Debit")
        assert str(a) == "1000 Cash and Bank"

    def test_fund_str(self):
        f = Fund(fund_id="FND-001", name="Community Health")
        assert str(f) == "FND-001 Community Health"

    def test_transaction_str_strips(self):
        t = Transaction(transaction_id="TRX-001", description="Grant receipt")
        assert str(t) == "TRX-001 Grant receipt"
        t2 = Transaction(transaction_id="TRX-002", description="")
        assert str(t2) == "TRX-002"

    def test_journal_entry_str(self):
        e = JournalEntry(journal_no="JV-007")
        assert str(e) == "JV-007"

    def test_journal_line_str(self, org):
        a = Account.objects.create(organization=org, code="1000", name="Cash", type="Asset", normal_balance="Debit")
        e = JournalEntry.objects.create(organization=org, journal_no="JV-1", date=datetime.date(2026, 1, 1))
        line = JournalLine.objects.create(journal=e, account=a, debit_idr=100, credit_idr=0)
        assert "100" in str(line)


@pytest.mark.django_db
class TestJournalTotals:
    def test_totals_sum_lines(self, org):
        a = Account.objects.create(organization=org, code="1000", name="Cash", type="Asset", normal_balance="Debit")
        b = Account.objects.create(organization=org, code="4000", name="Income", type="Income", normal_balance="Credit")
        e = JournalEntry.objects.create(organization=org, journal_no="JV-1", date=datetime.date(2026, 1, 1))
        JournalLine.objects.create(journal=e, account=a, debit_idr=500, credit_idr=0)
        JournalLine.objects.create(journal=e, account=b, debit_idr=0, credit_idr=500)
        debit, credit = e.totals()
        assert debit == Decimal("500")
        assert credit == Decimal("500")

    def test_empty_journal_totals_zero(self, org):
        e = JournalEntry.objects.create(organization=org, journal_no="JV-EMPTY", date=datetime.date(2026, 1, 1))
        assert e.totals() == (Decimal("0"), Decimal("0"))


@pytest.mark.django_db
class TestFundSerializer:
    def test_donor_label_prefers_company_name(self, org):
        from apps.companies.models import Company
        c = Company.objects.create(organization=org, name="Global Health Foundation")
        f = Fund.objects.create(organization=org, fund_id="FND-1", name="Health", donor=c)
        data = FundSerializer(f).data
        assert data["donor_label"] == "Global Health Foundation"

    def test_donor_label_falls_back_to_free_text(self, org):
        f = Fund.objects.create(organization=org, fund_id="FND-2", name="Edu", donor_name="Education Alliance")
        data = FundSerializer(f).data
        assert data["donor_label"] == "Education Alliance"

    def test_available_uses_annotated_spent(self, org):
        f = Fund.objects.create(organization=org, fund_id="FND-3", name="Climate", received_amount=Decimal("1000"))
        f._spent = Decimal("300")
        data = FundSerializer(f).data
        assert Decimal(str(data["spent_amount"])) == Decimal("300")
        assert Decimal(str(data["available_amount"])) == Decimal("700")

    def test_available_defaults_when_unannotated(self, org):
        f = Fund.objects.create(organization=org, fund_id="FND-4", name="Ops", received_amount=Decimal("500"))
        data = FundSerializer(f).data
        assert Decimal(str(data["spent_amount"])) == Decimal("0")
        assert Decimal(str(data["available_amount"])) == Decimal("500")


@pytest.mark.django_db
class TestJournalEntrySerializerWrite:
    def _accounts(self, org):
        cash = Account.objects.create(organization=org, code="1000", name="Cash", type="Asset", normal_balance="Debit")
        exp = Account.objects.create(organization=org, code="5000", name="Expense", type="Expense", normal_balance="Debit")
        return cash, exp

    def test_create_builds_nested_lines(self, org):
        cash, exp = self._accounts(org)
        s = JournalEntrySerializer(data={
            "journal_no": "JV-10", "date": "2026-01-01",
            "lines": [
                {"account": str(exp.id), "debit_idr": "100", "credit_idr": "0"},
                {"account": str(cash.id), "debit_idr": "0", "credit_idr": "100"},
            ],
        })
        assert s.is_valid(), s.errors
        entry = s.save(organization=org)
        assert entry.lines.count() == 2
        assert entry.totals() == (Decimal("100"), Decimal("100"))

    def test_update_replaces_lines(self, org):
        cash, exp = self._accounts(org)
        entry = JournalEntry.objects.create(organization=org, journal_no="JV-11", date=datetime.date(2026, 1, 1))
        JournalLine.objects.create(journal=entry, account=cash, debit_idr=999, credit_idr=0)
        s = JournalEntrySerializer(entry, data={
            "journal_no": "JV-11", "date": "2026-01-01",
            "lines": [
                {"account": str(exp.id), "debit_idr": "50", "credit_idr": "0"},
                {"account": str(cash.id), "debit_idr": "0", "credit_idr": "50"},
            ],
        }, partial=False)
        assert s.is_valid(), s.errors
        s.save()
        entry.refresh_from_db()
        assert entry.lines.count() == 2          # old 999 line gone
        assert entry.totals() == (Decimal("50"), Decimal("50"))

    def test_update_without_lines_keeps_them(self, org):
        cash, _ = self._accounts(org)
        entry = JournalEntry.objects.create(organization=org, journal_no="JV-12", date=datetime.date(2026, 1, 1))
        JournalLine.objects.create(journal=entry, account=cash, debit_idr=7, credit_idr=0)
        s = JournalEntrySerializer(entry, data={"description": "renamed"}, partial=True)
        assert s.is_valid(), s.errors
        s.save()
        entry.refresh_from_db()
        assert entry.lines.count() == 1
        assert entry.description == "renamed"


class TestRestrictionHelper:
    """views._restriction_class is pure — no DB."""

    def test_restricted_is_with(self):
        assert views._restriction_class("Restricted") == views.WITH

    @pytest.mark.parametrize("ftype", ["Unrestricted", "Designated", "", None])
    def test_others_are_without(self, ftype):
        assert views._restriction_class(ftype) == views.WITHOUT
