"""Balance Engine — centralized balance calculations."""
from __future__ import annotations

from decimal import Decimal
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.models.ledger import GeneralLedger, LedgerBalance, TrialBalanceSnapshot
from app.models.posting import JournalEntry
from app.models.types import JournalStatus
from app.repositories.foundation import AccountRepository
from app.repositories.ledger import (
    GeneralLedgerRepository,
    LedgerBalanceRepository,
    TrialBalanceSnapshotRepository,
)
from app.repositories.posting import JournalEntryRepository
from shared.exceptions import NotFoundError


class BalanceEngine:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.gl_repo = GeneralLedgerRepository(session)
        self.balance_repo = LedgerBalanceRepository(session)
        self.journal_repo = JournalEntryRepository(session)
        self.account_repo = AccountRepository(session)
        self.trial_repo = TrialBalanceSnapshotRepository(session)

    async def update_ledger_from_posting(
        self,
        tenant_id: UUID,
        voucher_id: UUID,
        fiscal_period_id: UUID,
        entries: list[JournalEntry],
    ) -> None:
        for entry in entries:
            balance = await self.balance_repo.get_by_account_period(
                tenant_id, entry.account_id, fiscal_period_id
            )
            current = balance.current_balance if balance else Decimal("0")
            running = current + entry.debit - entry.credit

            await self.gl_repo.add(
                GeneralLedger(
                    tenant_id=tenant_id,
                    account_id=entry.account_id,
                    fiscal_period_id=fiscal_period_id,
                    journal_entry_id=entry.id,
                    voucher_id=voucher_id,
                    entry_date=entry.entry_date,
                    debit=entry.debit,
                    credit=entry.credit,
                    running_balance=running,
                    description=entry.description,
                    cost_center_id=entry.cost_center_id,
                    project_id=entry.project_id,
                )
            )

            if balance is None:
                balance = LedgerBalance(
                    tenant_id=tenant_id,
                    account_id=entry.account_id,
                    fiscal_period_id=fiscal_period_id,
                    opening_balance=Decimal("0"),
                    debit_total=entry.debit,
                    credit_total=entry.credit,
                    closing_balance=running,
                    current_balance=running,
                )
                await self.balance_repo.add(balance)
            else:
                balance.debit_total += entry.debit
                balance.credit_total += entry.credit
                balance.current_balance = running
                balance.closing_balance = running

    async def get_account_balance(
        self, tenant_id: UUID, account_id: UUID, fiscal_period_id: UUID
    ) -> Decimal:
        balance = await self.balance_repo.get_by_account_period(
            tenant_id, account_id, fiscal_period_id
        )
        return balance.current_balance if balance else Decimal("0")

    async def generate_trial_balance(
        self, tenant_id: UUID, fiscal_period_id: UUID
    ) -> TrialBalanceSnapshot:
        balances = await self.balance_repo.list_by_period(tenant_id, fiscal_period_id)
        total_debit = sum(b.debit_total for b in balances)
        total_credit = sum(b.credit_total for b in balances)
        difference = total_debit - total_credit

        from datetime import date

        snapshot = TrialBalanceSnapshot(
            tenant_id=tenant_id,
            fiscal_period_id=fiscal_period_id,
            generated_at=date.today(),
            total_debit=total_debit,
            total_credit=total_credit,
            difference=difference,
            is_balanced=difference == Decimal("0"),
        )
        return await self.trial_repo.add(snapshot)

    async def recalculate_period(
        self, tenant_id: UUID, fiscal_period_id: UUID
    ) -> int:
        """Rebuild ledger balances from posted journal entries."""
        entries = await self.journal_repo.list_by_tenant(tenant_id, limit=10000)
        posted = [
            e for e in entries
            if e.fiscal_period_id == fiscal_period_id and e.status == JournalStatus.POSTED
        ]

        account_totals: dict[UUID, dict] = {}
        for entry in posted:
            if entry.account_id not in account_totals:
                account_totals[entry.account_id] = {
                    "debit": Decimal("0"),
                    "credit": Decimal("0"),
                }
            account_totals[entry.account_id]["debit"] += entry.debit
            account_totals[entry.account_id]["credit"] += entry.credit

        count = 0
        for account_id, totals in account_totals.items():
            balance = await self.balance_repo.get_by_account_period(
                tenant_id, account_id, fiscal_period_id
            )
            current = totals["debit"] - totals["credit"]
            if balance is None:
                balance = LedgerBalance(
                    tenant_id=tenant_id,
                    account_id=account_id,
                    fiscal_period_id=fiscal_period_id,
                    debit_total=totals["debit"],
                    credit_total=totals["credit"],
                    current_balance=current,
                    closing_balance=current,
                )
                await self.balance_repo.add(balance)
            else:
                balance.debit_total = totals["debit"]
                balance.credit_total = totals["credit"]
                balance.current_balance = current
                balance.closing_balance = current
            count += 1
        return count
