"""Sales accounting integration service."""
from __future__ import annotations

import json
from decimal import Decimal
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.models.posting import Voucher, VoucherLine
from app.models.sales_accounting import AccountingPreview, SalesPostingProfile
from app.models.types import SalesDocumentType, VoucherStatus
from app.repositories.base import TenantBaseRepository
from app.repositories.foundation import AccountRepository, FiscalPeriodRepository
from app.repositories.posting import VoucherRepository
from app.services.posting_engine import PostingEngine
from shared.exceptions import NotFoundError


class SalesPostingProfileRepository(TenantBaseRepository[SalesPostingProfile]):
    model = SalesPostingProfile


class AccountingPreviewRepository(TenantBaseRepository[AccountingPreview]):
    model = AccountingPreview


class SalesAccountingService:
    """Generates accounting entries for sales documents via Posting Engine only."""

    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.profile_repo = SalesPostingProfileRepository(session)
        self.preview_repo = AccountingPreviewRepository(session)
        self.voucher_repo = VoucherRepository(session)
        self.period_repo = FiscalPeriodRepository(session)
        self.account_repo = AccountRepository(session)
        self.posting_engine = PostingEngine(session)

    async def preview_sales_invoice(
        self,
        tenant_id: UUID,
        *,
        document_type: SalesDocumentType,
        total_amount: Decimal,
        discount_amount: Decimal = Decimal("0"),
        tax_amount: Decimal = Decimal("0"),
        profile_id: UUID | None = None,
    ) -> AccountingPreview:
        profile = await self._resolve_profile(tenant_id, document_type, profile_id)
        net_revenue = total_amount - discount_amount - tax_amount

        lines = []
        if profile.receivable_account_id:
            lines.append({"account_id": str(profile.receivable_account_id), "debit": str(total_amount), "credit": "0"})
        if profile.revenue_account_id:
            lines.append({"account_id": str(profile.revenue_account_id), "debit": "0", "credit": str(net_revenue)})
        if discount_amount > 0 and profile.discount_account_id:
            lines.append({"account_id": str(profile.discount_account_id), "debit": str(discount_amount), "credit": "0"})
        if tax_amount > 0 and profile.tax_account_id:
            lines.append({"account_id": str(profile.tax_account_id), "debit": "0", "credit": str(tax_amount)})

        total_debit = sum(Decimal(l["debit"]) for l in lines)
        total_credit = sum(Decimal(l["credit"]) for l in lines)

        preview = AccountingPreview(
            tenant_id=tenant_id,
            source_module="sales",
            source_document_type=document_type.value,
            source_document_id="preview",
            preview_data=json.dumps(lines),
            is_balanced=total_debit == total_credit,
            total_debit=total_debit,
            total_credit=total_credit,
        )
        return await self.preview_repo.add(preview)

    async def post_sales_invoice(
        self,
        tenant_id: UUID,
        *,
        document_type: SalesDocumentType,
        source_document_id: str,
        total_amount: Decimal,
        discount_amount: Decimal = Decimal("0"),
        tax_amount: Decimal = Decimal("0"),
        profile_id: UUID | None = None,
        actor_user_id: str,
        voucher_date=None,
    ) -> Voucher:
        from datetime import date

        from app.services.source_posting_guard import SourcePostingGuard

        guard = SourcePostingGuard(self.session)
        await guard.assert_can_post(
            tenant_id,
            source_module="sales",
            source_document_type=document_type.value,
            source_document_id=source_document_id,
        )

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

        voucher_number = f"SINV-{source_document_id}"
        voucher = Voucher(
            tenant_id=tenant_id,
            fiscal_period_id=period.id,
            voucher_number=voucher_number,
            voucher_date=voucher_date or date.today(),
            status=VoucherStatus.DRAFT,
            description=f"Sales {document_type.value} {source_document_id}",
            source_module="sales",
            reference_number=source_document_id,
            created_by=actor_user_id,
        )
        await self.voucher_repo.add(voucher)

        net_revenue = total_amount - discount_amount - tax_amount
        line_num = 0

        if profile.receivable_account_id:
            line_num += 1
            self.session.add(VoucherLine(
                tenant_id=tenant_id, voucher_id=voucher.id, line_number=line_num,
                account_id=profile.receivable_account_id, debit=total_amount, credit=Decimal("0"),
            ))
        if profile.revenue_account_id:
            line_num += 1
            self.session.add(VoucherLine(
                tenant_id=tenant_id, voucher_id=voucher.id, line_number=line_num,
                account_id=profile.revenue_account_id, debit=Decimal("0"), credit=net_revenue,
            ))
        if discount_amount > 0 and profile.discount_account_id:
            line_num += 1
            self.session.add(VoucherLine(
                tenant_id=tenant_id, voucher_id=voucher.id, line_number=line_num,
                account_id=profile.discount_account_id, debit=discount_amount, credit=Decimal("0"),
            ))
        if tax_amount > 0 and profile.tax_account_id:
            line_num += 1
            self.session.add(VoucherLine(
                tenant_id=tenant_id, voucher_id=voucher.id, line_number=line_num,
                account_id=profile.tax_account_id, debit=Decimal("0"), credit=tax_amount,
            ))

        await self.session.flush()
        if await guard.should_post_immediately(tenant_id):
            voucher = await self.posting_engine.post_voucher(
                tenant_id, voucher.id, actor_user_id=actor_user_id, source_module="sales"
            )
        await guard.mark_posted(
            tenant_id,
            voucher_id=voucher.id,
            source_module="sales",
            source_document_type=document_type.value,
            source_document_id=source_document_id,
        )
        return voucher

    async def _resolve_profile(
        self, tenant_id: UUID, document_type: SalesDocumentType, profile_id: UUID | None
    ) -> SalesPostingProfile:
        if profile_id:
            profile = await self.profile_repo.get(tenant_id, profile_id)
            if profile is None:
                raise NotFoundError("پروفایل ثبت فروش یافت نشد", error_code="profile_not_found")
            return profile
        from sqlalchemy import select
        stmt = select(SalesPostingProfile).where(
            SalesPostingProfile.tenant_id == tenant_id,
            SalesPostingProfile.document_type == document_type,
            SalesPostingProfile.is_default.is_(True),
        )
        result = await self.session.execute(stmt)
        profile = result.scalar_one_or_none()
        if profile is None:
            raise NotFoundError("پروفایل پیش‌فرض ثبت فروش یافت نشد", error_code="profile_not_found")
        return profile
