"""Fiscal period management service."""
from __future__ import annotations

from calendar import monthrange
from datetime import date
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.models.foundation import FiscalPeriod, FiscalYear
from app.models.types import FiscalPeriodStatus
from app.repositories.foundation import FiscalPeriodRepository, FiscalYearRepository
from shared.exceptions import AppError, NotFoundError


class FiscalManagementError(AppError):
    status_code = 422
    error_code = "fiscal_management_error"


class FiscalManagementService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.year_repo = FiscalYearRepository(session)
        self.period_repo = FiscalPeriodRepository(session)

    async def lock_period(self, tenant_id: UUID, period_id: UUID) -> FiscalPeriod:
        period = await self.period_repo.get(tenant_id, period_id)
        if period is None:
            raise NotFoundError("دوره مالی یافت نشد", error_code="period_not_found")
        if period.status != FiscalPeriodStatus.OPEN:
            raise FiscalManagementError("فقط دوره‌های باز قابل قفل هستند")
        period.status = FiscalPeriodStatus.LOCKED
        return period

    async def close_period(self, tenant_id: UUID, period_id: UUID) -> FiscalPeriod:
        period = await self.period_repo.get(tenant_id, period_id)
        if period is None:
            raise NotFoundError("دوره مالی یافت نشد", error_code="period_not_found")
        period.status = FiscalPeriodStatus.CLOSED
        period.is_current = False
        return period

    async def reopen_period(self, tenant_id: UUID, period_id: UUID) -> FiscalPeriod:
        period = await self.period_repo.get(tenant_id, period_id)
        if period is None:
            raise NotFoundError("دوره مالی یافت نشد", error_code="period_not_found")
        if period.status == FiscalPeriodStatus.ARCHIVED:
            raise FiscalManagementError("دوره بایگانی‌شده قابل بازگشایی نیست")
        period.status = FiscalPeriodStatus.OPEN
        return period

    async def close_fiscal_year(self, tenant_id: UUID, year_id: UUID) -> FiscalYear:
        year = await self.year_repo.get(tenant_id, year_id)
        if year is None:
            raise NotFoundError("سال مالی یافت نشد", error_code="year_not_found")
        periods = await self.period_repo.list_by_year(tenant_id, year_id)
        for p in periods:
            if p.status == FiscalPeriodStatus.OPEN:
                p.status = FiscalPeriodStatus.CLOSED
            p.is_current = False
        year.is_closed = True
        year.is_current = False
        return year

    async def set_current_year(self, tenant_id: UUID, year_id: UUID) -> FiscalYear:
        year = await self.year_repo.get(tenant_id, year_id)
        if year is None:
            raise NotFoundError("سال مالی یافت نشد", error_code="year_not_found")
        if year.is_closed:
            raise FiscalManagementError("سال بسته‌شده نمی‌تواند جاری باشد")
        for y in await self.year_repo.list_by_tenant(tenant_id, limit=500):
            y.is_current = y.id == year_id
        return year

    async def set_current_period(self, tenant_id: UUID, period_id: UUID) -> FiscalPeriod:
        period = await self.period_repo.get(tenant_id, period_id)
        if period is None:
            raise NotFoundError("دوره مالی یافت نشد", error_code="period_not_found")
        if period.status == FiscalPeriodStatus.CLOSED:
            raise FiscalManagementError("دوره بسته‌شده نمی‌تواند جاری باشد")
        for p in await self.period_repo.list_by_tenant(tenant_id, limit=500):
            p.is_current = p.id == period_id
        return period

    async def generate_monthly_periods(
        self, tenant_id: UUID, year_id: UUID
    ) -> list[FiscalPeriod]:
        year = await self.year_repo.get(tenant_id, year_id)
        if year is None:
            raise NotFoundError("سال مالی یافت نشد", error_code="year_not_found")
        existing = await self.period_repo.list_by_year(tenant_id, year_id)
        if existing:
            raise FiscalManagementError("برای این سال قبلاً دوره تعریف شده است")

        periods: list[FiscalPeriod] = []
        cursor = year.start_date.replace(day=1)
        end = year.end_date
        num = 1
        while cursor <= end:
            last_day = monthrange(cursor.year, cursor.month)[1]
            period_end = date(cursor.year, cursor.month, last_day)
            if period_end > end:
                period_end = end
            period_start = year.start_date if num == 1 else cursor
            if period_start > period_end:
                break
            entity = FiscalPeriod(
                tenant_id=tenant_id,
                fiscal_year_id=year_id,
                name=f"دوره {num}",
                period_number=num,
                start_date=period_start,
                end_date=period_end,
                status=FiscalPeriodStatus.OPEN,
                is_current=num == 1 and year.is_current,
            )
            await self.period_repo.add(entity)
            periods.append(entity)
            num += 1
            if period_end >= end:
                break
            if cursor.month == 12:
                cursor = date(cursor.year + 1, 1, 1)
            else:
                cursor = date(cursor.year, cursor.month + 1, 1)
        await self.session.flush()
        return periods
