"""Settlement Engine for AR/AP."""
from __future__ import annotations

from decimal import Decimal
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.models.receivable_payable import (
    ReceivableInvoice,
    PayableInvoice,
    Settlement,
    SettlementAllocation,
)
from app.models.types import InvoiceStatus, SettlementStatus
from app.repositories.base import TenantBaseRepository
from shared.exceptions import AppError, NotFoundError


class SettlementError(AppError):
    status_code = 422
    error_code = "settlement_error"


class ReceivableInvoiceRepository(TenantBaseRepository[ReceivableInvoice]):
    model = ReceivableInvoice


class PayableInvoiceRepository(TenantBaseRepository[PayableInvoice]):
    model = PayableInvoice


class SettlementRepository(TenantBaseRepository[Settlement]):
    model = Settlement


class SettlementEngine:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.receivable_repo = ReceivableInvoiceRepository(session)
        self.payable_repo = PayableInvoiceRepository(session)
        self.settlement_repo = SettlementRepository(session)

    async def allocate_receivable(
        self,
        tenant_id: UUID,
        settlement: Settlement,
        allocations: list[dict],
    ) -> Settlement:
        if settlement.status == SettlementStatus.COMPLETED:
            raise SettlementError("تسویه قبلاً انجام شده است")

        total_allocated = Decimal("0")
        for alloc in allocations:
            invoice = await self.receivable_repo.get(tenant_id, alloc["invoice_id"])
            if invoice is None:
                raise NotFoundError("فاکتور دریافتنی یافت نشد", error_code="invoice_not_found")
            amount = Decimal(str(alloc["amount"]))
            if amount > invoice.remaining_amount:
                raise SettlementError(f"مبلغ تخصیص بیش از مانده فاکتور {invoice.invoice_number}")

            self.session.add(
                SettlementAllocation(
                    tenant_id=tenant_id,
                    settlement_id=settlement.id,
                    invoice_type="receivable",
                    invoice_id=invoice.id,
                    allocated_amount=amount,
                )
            )
            invoice.paid_amount += amount
            invoice.remaining_amount -= amount
            if invoice.remaining_amount == 0:
                invoice.status = InvoiceStatus.PAID
            else:
                invoice.status = InvoiceStatus.PARTIALLY_PAID
            total_allocated += amount

        if total_allocated != settlement.total_amount:
            raise SettlementError("مجموع تخصیص با مبلغ تسویه برابر نیست")

        settlement.status = SettlementStatus.COMPLETED
        return settlement

    async def allocate_payable(
        self,
        tenant_id: UUID,
        settlement: Settlement,
        allocations: list[dict],
    ) -> Settlement:
        if settlement.status == SettlementStatus.COMPLETED:
            raise SettlementError("تسویه قبلاً انجام شده است")

        total_allocated = Decimal("0")
        for alloc in allocations:
            invoice = await self.payable_repo.get(tenant_id, alloc["invoice_id"])
            if invoice is None:
                raise NotFoundError("فاکتور پرداختنی یافت نشد", error_code="invoice_not_found")
            amount = Decimal(str(alloc["amount"]))
            if amount > invoice.remaining_amount:
                raise SettlementError(f"مبلغ تخصیص بیش از مانده فاکتور {invoice.invoice_number}")

            self.session.add(
                SettlementAllocation(
                    tenant_id=tenant_id,
                    settlement_id=settlement.id,
                    invoice_type="payable",
                    invoice_id=invoice.id,
                    allocated_amount=amount,
                )
            )
            invoice.paid_amount += amount
            invoice.remaining_amount -= amount
            if invoice.remaining_amount == 0:
                invoice.status = InvoiceStatus.PAID
            else:
                invoice.status = InvoiceStatus.PARTIALLY_PAID
            total_allocated += amount

        if total_allocated != settlement.total_amount:
            raise SettlementError("مجموع تخصیص با مبلغ تسویه برابر نیست")

        settlement.status = SettlementStatus.COMPLETED
        return settlement
