"""Phase 5.8 — Depreciation Engine & Asset accounting."""
from __future__ import annotations

from datetime import date
from decimal import Decimal, ROUND_HALF_UP
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.models.fixed_assets import Asset, AssetDepreciation, AssetHistory, DepreciationSchedule
from app.models.posting import Voucher, VoucherLine
from app.models.types import AssetStatus, DepreciationMethod, VoucherStatus
from app.repositories.base import TenantBaseRepository
from app.repositories.foundation import FiscalPeriodRepository
from app.repositories.posting import VoucherRepository
from app.services.posting_engine import PostingEngine
from shared.exceptions import AppError, NotFoundError


class AssetRepo(TenantBaseRepository[Asset]):
    model = Asset


class DepreciationScheduleRepo(TenantBaseRepository[DepreciationSchedule]):
    model = DepreciationSchedule


class AssetDepreciationRepo(TenantBaseRepository[AssetDepreciation]):
    model = AssetDepreciation


class AssetAccountingError(AppError):
    status_code = 422
    error_code = "asset_accounting_error"


class DepreciationEngine:
    """Centralized depreciation calculation engine."""

    def calculate(
        self,
        asset: Asset,
        *,
        period_months: int = 1,
    ) -> Decimal:
        depreciable = asset.acquisition_cost - asset.residual_value
        if depreciable <= 0 or asset.useful_life_months <= 0:
            return Decimal("0")

        if asset.depreciation_method == DepreciationMethod.STRAIGHT_LINE:
            monthly = depreciable / asset.useful_life_months
            return (monthly * period_months).quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP)

        if asset.depreciation_method == DepreciationMethod.DECLINING_BALANCE:
            rate = Decimal("2") / asset.useful_life_months
            return (asset.current_book_value * rate * period_months).quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP)

        if asset.depreciation_method == DepreciationMethod.DOUBLE_DECLINING:
            rate = Decimal("2") / asset.useful_life_months * 2
            return (asset.current_book_value * rate * period_months).quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP)

        return (depreciable / asset.useful_life_months * period_months).quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP)

    def generate_schedule(self, asset: Asset) -> list[dict]:
        schedule = []
        book_value = asset.acquisition_cost
        for period in range(1, asset.useful_life_months + 1):
            amount = self.calculate(asset, period_months=1)
            if book_value - amount < asset.residual_value:
                amount = book_value - asset.residual_value
            if amount <= 0:
                break
            schedule.append({"period": period, "amount": amount, "book_value_after": book_value - amount})
            book_value -= amount
        return schedule


class AssetAccountingService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.asset_repo = AssetRepo(session)
        self.schedule_repo = DepreciationScheduleRepo(session)
        self.depreciation_repo = AssetDepreciationRepo(session)
        self.voucher_repo = VoucherRepository(session)
        self.period_repo = FiscalPeriodRepository(session)
        self.posting_engine = PostingEngine(session)
        self.depreciation_engine = DepreciationEngine()

    async def activate_asset(self, tenant_id: UUID, asset_id: UUID, actor_user_id: str) -> Asset:
        asset = await self.asset_repo.get(tenant_id, asset_id)
        if asset is None:
            raise NotFoundError("دارایی یافت نشد", error_code="asset_not_found")
        if asset.status != AssetStatus.DRAFT:
            raise AssetAccountingError("فقط دارایی‌های پیش‌نویس قابل فعال‌سازی هستند")
        asset.status = AssetStatus.ACTIVE
        asset.current_book_value = asset.acquisition_cost
        self._record_history(tenant_id, asset_id, "activated", asset.acquisition_cost, asset.acquisition_cost)
        return asset

    async def depreciate_asset(
        self,
        tenant_id: UUID,
        asset_id: UUID,
        *,
        expense_account_id: UUID,
        accumulated_account_id: UUID,
        actor_user_id: str,
    ) -> AssetDepreciation:
        asset = await self.asset_repo.get(tenant_id, asset_id)
        if asset is None:
            raise NotFoundError("دارایی یافت نشد", error_code="asset_not_found")
        if asset.status not in (AssetStatus.ACTIVE, AssetStatus.DEPRECIATING):
            raise AssetAccountingError("دارایی فعال نیست")

        amount = self.depreciation_engine.calculate(asset)
        if amount <= 0:
            raise AssetAccountingError("مبلغ استهلاک صفر است")

        period = await self.period_repo.get_current(tenant_id)
        if period is None:
            raise NotFoundError("دوره مالی جاری یافت نشد", error_code="period_not_found")

        voucher = Voucher(
            tenant_id=tenant_id,
            fiscal_period_id=period.id,
            voucher_number=f"DEP-{asset.code}-{date.today().isoformat()}",
            voucher_date=date.today(),
            status=VoucherStatus.DRAFT,
            description=f"Depreciation {asset.code}",
            source_module="assets",
            reference_number=str(asset_id),
            created_by=actor_user_id,
        )
        await self.voucher_repo.add(voucher)
        self.session.add(VoucherLine(
            tenant_id=tenant_id, voucher_id=voucher.id, line_number=1,
            account_id=expense_account_id, debit=amount, credit=Decimal("0"),
        ))
        self.session.add(VoucherLine(
            tenant_id=tenant_id, voucher_id=voucher.id, line_number=2,
            account_id=accumulated_account_id, debit=Decimal("0"), credit=amount,
        ))
        await self.session.flush()
        posted = await self.posting_engine.post_voucher(
            tenant_id, voucher.id, actor_user_id=actor_user_id, source_module="assets"
        )

        accumulated = asset.acquisition_cost - asset.current_book_value + amount
        asset.current_book_value -= amount
        asset.status = AssetStatus.DEPRECIATING if asset.current_book_value > asset.residual_value else AssetStatus.FULLY_DEPRECIATED

        dep = AssetDepreciation(
            tenant_id=tenant_id,
            asset_id=asset_id,
            fiscal_period_id=period.id,
            depreciation_date=date.today(),
            amount=amount,
            accumulated_depreciation=accumulated,
            book_value_after=asset.current_book_value,
            voucher_id=posted.id,
        )
        return await self.depreciation_repo.add(dep)

    def _record_history(
        self, tenant_id: UUID, asset_id: UUID, event_type: str,
        before: Decimal, after: Decimal,
    ) -> None:
        self.session.add(AssetHistory(
            tenant_id=tenant_id,
            asset_id=asset_id,
            event_type=event_type,
            event_date=date.today(),
            before_value=str(before),
            after_value=str(after),
        ))
