"""Phase 5.9 — Payroll Engine & accounting integration."""
from __future__ import annotations

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

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.payroll import (
    Employee,
    EmployeeAdvance,
    EmployeeLoan,
    Payroll,
    PayrollAccountingProfile,
    PayrollItem,
    PayrollPeriod,
)
from app.models.posting import Voucher, VoucherLine
from app.models.types import PayrollPeriodStatus, PayrollStatus, 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 PayrollError(AppError):
    status_code = 422
    error_code = "payroll_error"


class EmployeeRepo(TenantBaseRepository[Employee]):
    model = Employee


class PayrollPeriodRepo(TenantBaseRepository[PayrollPeriod]):
    model = PayrollPeriod


class PayrollRepo(TenantBaseRepository[Payroll]):
    model = Payroll


class PayrollProfileRepo(TenantBaseRepository[PayrollAccountingProfile]):
    model = PayrollAccountingProfile


class PayrollEngine:
    """Centralized payroll calculation engine."""

    def calculate(
        self,
        employee: Employee,
        *,
        benefits: Decimal = Decimal("0"),
        deductions: Decimal = Decimal("0"),
        overtime: Decimal = Decimal("0"),
    ) -> dict:
        gross = employee.base_salary + benefits + overtime
        net = gross - deductions
        employer_cost = gross + benefits * Decimal("0.23")
        return {
            "gross_salary": gross,
            "total_benefits": benefits,
            "total_deductions": deductions,
            "net_salary": net,
            "employer_cost": employer_cost,
        }


class PayrollAccountingService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.employee_repo = EmployeeRepo(session)
        self.period_repo = PayrollPeriodRepo(session)
        self.payroll_repo = PayrollRepo(session)
        self.profile_repo = PayrollProfileRepo(session)
        self.fiscal_period_repo = FiscalPeriodRepository(session)
        self.voucher_repo = VoucherRepository(session)
        self.posting_engine = PostingEngine(session)
        self.payroll_engine = PayrollEngine()

    async def calculate_payroll(
        self, tenant_id: UUID, payroll_period_id: UUID, employee_id: UUID
    ) -> Payroll:
        employee = await self.employee_repo.get(tenant_id, employee_id)
        if employee is None:
            raise NotFoundError("کارمند یافت نشد", error_code="employee_not_found")

        period = await self.period_repo.get(tenant_id, payroll_period_id)
        if period is None:
            raise NotFoundError("دوره حقوق یافت نشد", error_code="payroll_period_not_found")
        if period.status not in (PayrollPeriodStatus.OPEN, PayrollPeriodStatus.PROCESSING):
            raise PayrollError("دوره حقوق قابل محاسبه نیست")

        calc = self.payroll_engine.calculate(employee)
        payroll = Payroll(
            tenant_id=tenant_id,
            payroll_period_id=payroll_period_id,
            employee_id=employee_id,
            gross_salary=calc["gross_salary"],
            total_deductions=calc["total_deductions"],
            total_benefits=calc["total_benefits"],
            net_salary=calc["net_salary"],
            employer_cost=calc["employer_cost"],
            status=PayrollStatus.CALCULATED,
        )
        return await self.payroll_repo.add(payroll)

    async def post_payroll(
        self,
        tenant_id: UUID,
        payroll_id: UUID,
        *,
        actor_user_id: str,
        profile_id: UUID | None = None,
    ) -> Payroll:
        payroll = await self.payroll_repo.get(tenant_id, payroll_id)
        if payroll is None:
            raise NotFoundError("حقوق یافت نشد", error_code="payroll_not_found")
        if payroll.status != PayrollStatus.APPROVED:
            raise PayrollError("حقوق باید تأیید شده باشد")

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

        voucher = Voucher(
            tenant_id=tenant_id,
            fiscal_period_id=fiscal_period.id,
            voucher_number=f"PAY-{payroll_id}",
            voucher_date=date.today(),
            status=VoucherStatus.DRAFT,
            description=f"Payroll posting {payroll_id}",
            source_module="payroll",
            reference_number=str(payroll_id),
            created_by=actor_user_id,
        )
        await self.voucher_repo.add(voucher)

        if profile.salary_expense_account_id and profile.payable_account_id:
            self.session.add(VoucherLine(
                tenant_id=tenant_id, voucher_id=voucher.id, line_number=1,
                account_id=profile.salary_expense_account_id,
                debit=payroll.gross_salary, credit=Decimal("0"),
            ))
            self.session.add(VoucherLine(
                tenant_id=tenant_id, voucher_id=voucher.id, line_number=2,
                account_id=profile.payable_account_id,
                debit=Decimal("0"), credit=payroll.net_salary,
            ))
            if payroll.total_deductions > 0 and profile.deduction_account_id:
                self.session.add(VoucherLine(
                    tenant_id=tenant_id, voucher_id=voucher.id, line_number=3,
                    account_id=profile.deduction_account_id,
                    debit=Decimal("0"), credit=payroll.total_deductions,
                ))
        await self.session.flush()
        posted = await self.posting_engine.post_voucher(
            tenant_id, voucher.id, actor_user_id=actor_user_id, source_module="payroll"
        )
        payroll.status = PayrollStatus.PAID
        payroll.voucher_id = posted.id
        return payroll

    async def _resolve_profile(
        self, tenant_id: UUID, profile_id: UUID | None
    ) -> PayrollAccountingProfile:
        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
        stmt = select(PayrollAccountingProfile).where(
            PayrollAccountingProfile.tenant_id == tenant_id,
            PayrollAccountingProfile.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
