from sqlalchemy.exc import DBAPIError

from app.sales.fifo_repository import SalesFifoRepository
from app.sales.schemas import (
    SalesBatchLockRequest,
    SalesBatchLockResponse,
    SalesFifoRepostResponse,
    SalesLockResponse,
)


class FifoPostingError(Exception):
    pass


class FifoWriteDisabledError(Exception):
    pass


class FifoNonTransactionalTablesError(Exception):
    def __init__(self, tables: list[str]) -> None:
        self.tables = tables


class FifoDocumentNotFoundError(Exception):
    pass


class FifoDocumentStateError(Exception):
    pass


class FifoAuthorizationError(Exception):
    pass


class FifoSyntheticCostError(Exception):
    pass


class SalesFifoService:
    def __init__(
        self,
        repository: SalesFifoRepository,
        *,
        write_enabled: bool = True,
    ) -> None:
        self._repository = repository
        self._write_enabled = write_enabled

    async def _ensure_writes_are_safe(self) -> None:
        if not self._write_enabled:
            raise FifoWriteDisabledError()
        tables = await self._repository.non_transactional_tables()
        if tables:
            raise FifoNonTransactionalTablesError(tables)
        await self._repository.session.rollback()

    async def post_and_sync(self, *, kodesi: str, user_code: str) -> None:
        try:
            await self._repository.post(kodesi=kodesi, user_code=user_code)
        except DBAPIError as exc:
            if "Stok FIFO tidak cukup" in str(exc):
                raise FifoPostingError(
                    "Stok FIFO tidak cukup untuk penjualan"
                ) from exc
            raise
        await self._repository.sync_detail_hpp(kodesi)
        await self._repository.sync_balances(
            kodesi=kodesi, user_code=user_code
        )

    async def rollback_and_sync(self, *, kodesi: str, user_code: str) -> None:
        await self._repository.rollback(kodesi=kodesi, user_code=user_code)
        await self._repository.sync_balances(
            kodesi=kodesi, user_code=user_code
        )

    async def get_document_pairs(self, kodesi: str) -> list[dict[str, str]]:
        return await self._repository.get_document_pairs(kodesi)

    async def sync_balance_pairs(
        self, *, pairs: list[dict[str, str]], user_code: str
    ) -> None:
        await self._repository.sync_balance_pairs(
            pairs=pairs, user_code=user_code
        )

    async def lock(
        self,
        *,
        kodesi: str,
        user_code: str,
        user_name: str,
    ) -> SalesLockResponse:
        await self._ensure_writes_are_safe()
        async with self._repository.session.begin():
            header = await self._repository.get_header_for_update(kodesi)
            if header is None:
                raise FifoDocumentNotFoundError(kodesi)
            if str(header["tutup"]) == "1":
                raise FifoDocumentStateError("Dokumen sudah dikunci")
            await self.post_and_sync(kodesi=kodesi, user_code=user_code)
            await self._repository.set_locked(kodesi=kodesi, locked=True)
            await self._repository.add_audit(
                user_code=user_code,
                user_name=user_name,
                kodesi=kodesi,
                audit_status="TUTUP PENJUALAN",
            )
        return SalesLockResponse(
            kodesi=kodesi, tutup=True, fifo_status="posted"
        )

    async def unlock(
        self,
        *,
        kodesi: str,
        user_code: str,
        user_name: str,
        user_role: str,
    ) -> SalesLockResponse:
        if user_role.upper() not in {"SUPERVISOR", "OWNER"}:
            raise FifoAuthorizationError()
        await self._ensure_writes_are_safe()
        async with self._repository.session.begin():
            header = await self._repository.get_header_for_update(kodesi)
            if header is None:
                raise FifoDocumentNotFoundError(kodesi)
            if str(header["tutup"]) != "1":
                raise FifoDocumentStateError("Dokumen belum dikunci")
            await self.rollback_and_sync(kodesi=kodesi, user_code=user_code)
            await self._repository.set_locked(kodesi=kodesi, locked=False)
            await self._repository.add_audit(
                user_code=user_code,
                user_name=user_name,
                kodesi=kodesi,
                audit_status="BUKA PENJUALAN",
            )
        return SalesLockResponse(
            kodesi=kodesi, tutup=False, fifo_status="rolled_back"
        )

    async def batch_lock(
        self,
        *,
        request: SalesBatchLockRequest,
        user_code: str,
        user_name: str,
    ) -> SalesBatchLockResponse:
        await self._ensure_writes_are_safe()
        async with self._repository.session.begin():
            documents = await self._repository.get_batch_for_update(
                tanggal_mulai=request.tanggal_mulai,
                tanggal_selesai=request.tanggal_selesai,
                kode_divisi=request.kode_divisi,
            )
            for kodesi in documents:
                await self.post_and_sync(
                    kodesi=kodesi, user_code=user_code
                )
                await self._repository.set_locked(
                    kodesi=kodesi, locked=True
                )
                await self._repository.add_audit(
                    user_code=user_code,
                    user_name=user_name,
                    kodesi=kodesi,
                    audit_status="TUTUP PENJUALAN BATCH",
                )
        return SalesBatchLockResponse(
            locked_count=len(documents), documents=documents
        )

    async def developer_repost(
        self,
        *,
        kodesi: str,
        user_code: str,
        user_name: str,
        user_role: str,
    ) -> SalesFifoRepostResponse:
        if user_role.upper() != "OWNER":
            raise FifoAuthorizationError()
        await self._ensure_writes_are_safe()
        async with self._repository.session.begin():
            header = await self._repository.get_header_for_update(kodesi)
            if header is None:
                raise FifoDocumentNotFoundError(kodesi)
            await self._repository.lock_document_layers(kodesi)
            shortages = await self._repository.get_document_shortages(kodesi)
            missing_cost = [
                row for row in shortages if not row.get("hpp_unit")
            ]
            if missing_cost:
                products = ", ".join(
                    f"{row['kodebarang']}@{row['kodelokasi']}"
                    for row in missing_cost
                )
                raise FifoSyntheticCostError(
                    f"HPP layer sintetis tidak tersedia: {products}"
                )
            await self._repository.insert_developer_layers(
                kodesi=kodesi,
                shortages=shortages,
                user_code=user_code,
            )
            await self.post_and_sync(kodesi=kodesi, user_code=user_code)
            await self._repository.add_audit(
                user_code=user_code,
                user_name=user_name,
                kodesi=kodesi,
                audit_status="REPOST FIFO PENJUALAN",
            )
        return SalesFifoRepostResponse(
            kodesi=kodesi,
            fifo_status="reposted",
            synthetic_layer_created=bool(shortages),
        )
