from datetime import date

from app.customers.repository import CustomerRepository
from app.customers.schemas import (
    CustomerCodePreview,
    CustomerDeleteResponse,
    CustomerDetail,
    CustomerListItem,
    CustomerListResponse,
    CustomerWriteRequest,
    CustomerWriteResponse,
    MasterLookup,
    ReceivableNotificationItem,
    ReceivableNotificationResponse,
    ReceivableSummary,
)
from app.sales.schemas import Pagination


class CustomerNotFoundError(Exception):
    pass


class CustomerAlreadyExistsError(Exception):
    pass


class CustomerInUseError(Exception):
    pass


class CustomerWriteDisabledError(Exception):
    pass


class CustomerTableNotTransactionalError(Exception):
    pass


class CustomerService:
    def __init__(self, repository: CustomerRepository, *, write_enabled: bool) -> None:
        self._repository = repository
        self._write_enabled = write_enabled

    async def list_customers(self, **kwargs: object) -> CustomerListResponse:
        page = int(kwargs.pop("page"))
        page_size = int(kwargs.pop("page_size"))
        rows, total = await self._repository.list_customers(
            **kwargs, limit=page_size, offset=(page - 1) * page_size
        )
        return CustomerListResponse(
            data=[CustomerListItem.model_validate(row) for row in rows],
            pagination=Pagination(
                page=page, page_size=page_size, total=total,
                total_pages=(total + page_size - 1) // page_size,
            ),
        )

    async def get_customer(
        self, kode_pelanggan: str, *, kode_sales: str | None = None
    ) -> CustomerDetail | None:
        row = await self._repository.get_customer(
            kode_pelanggan, kode_sales=kode_sales
        )
        return CustomerDetail.model_validate(row) if row else None

    async def preview_code(self, name: str) -> CustomerCodePreview:
        return CustomerCodePreview(kode_pelanggan=await self._repository.next_code(name))

    async def list_lookup(self, kind: str, search: str | None) -> list[MasterLookup]:
        return [
            MasterLookup.model_validate(row)
            for row in await self._repository.list_lookup(kind, search)
        ]

    async def _ensure_writable(self) -> None:
        if not self._write_enabled:
            raise CustomerWriteDisabledError
        if (await self._repository.table_engine() or "").upper() != "INNODB":
            raise CustomerTableNotTransactionalError

    async def create(
        self, request: CustomerWriteRequest, user_code: str, user_name: str
    ) -> CustomerWriteResponse:
        await self._ensure_writable()
        await self._repository.session.rollback()
        async with self._repository.session.begin():
            lock_acquired = await self._repository.acquire_code_lock()
            if not lock_acquired:
                raise TimeoutError("Gagal memperoleh lock nomor pelanggan")
            try:
                kode = request.kode_pelanggan or await self._repository.next_code(
                    request.nama_pelanggan
                )
                if await self._repository.customer_exists(kode):
                    raise CustomerAlreadyExistsError
                await self._repository.insert_customer(kode, request, date.today())
                await self._repository.add_audit(
                    user_code=user_code, user_name=user_name, kode=kode,
                    status="TAMBAH PELANGGAN",
                )
            finally:
                await self._repository.release_code_lock()
        return CustomerWriteResponse(kode_pelanggan=kode, message="Pelanggan dibuat")

    async def update(
        self, kode: str, request: CustomerWriteRequest, user_code: str, user_name: str
    ) -> CustomerWriteResponse:
        await self._ensure_writable()
        await self._repository.session.rollback()
        async with self._repository.session.begin():
            if not await self._repository.update_customer(kode, request, date.today()):
                raise CustomerNotFoundError
            await self._repository.add_audit(
                user_code=user_code, user_name=user_name, kode=kode,
                status="EDIT PELANGGAN",
            )
        return CustomerWriteResponse(kode_pelanggan=kode, message="Pelanggan diperbarui")

    async def delete(
        self, kode: str, user_code: str, user_name: str
    ) -> CustomerDeleteResponse:
        await self._ensure_writable()
        await self._repository.session.rollback()
        async with self._repository.session.begin():
            if await self._repository.is_used_in_sales(kode):
                raise CustomerInUseError
            if not await self._repository.delete_customer(kode):
                raise CustomerNotFoundError
            await self._repository.add_audit(
                user_code=user_code, user_name=user_name, kode=kode,
                status="HAPUS PELANGGAN",
            )
        return CustomerDeleteResponse(kode_pelanggan=kode, message="Pelanggan dihapus")

    async def receivable_summary(
        self, kode: str, *, kode_sales: str | None = None
    ) -> ReceivableSummary:
        if not await self._repository.customer_exists(kode):
            raise CustomerNotFoundError
        if kode_sales:
            cust = await self._repository.get_customer(kode, kode_sales=kode_sales)
            if cust is None:
                raise CustomerNotFoundError
        row = await self._repository.receivable_summary(kode, kode_sales=kode_sales)
        return ReceivableSummary(kode_pelanggan=kode, **row)

    async def list_notifications(self, **kwargs: object) -> ReceivableNotificationResponse:
        page = int(kwargs.pop("page"))
        page_size = int(kwargs.pop("page_size"))
        rows, total = await self._repository.list_notifications(
            **kwargs, limit=page_size, offset=(page - 1) * page_size
        )
        return ReceivableNotificationResponse(
            data=[ReceivableNotificationItem.model_validate(row) for row in rows],
            pagination=Pagination(
                page=page, page_size=page_size, total=total,
                total_pages=(total + page_size - 1) // page_size,
            ),
        )

    async def whatsapp_phone(self, kode: str) -> str | None:
        if not await self._repository.customer_exists(kode):
            raise CustomerNotFoundError
        return await self._repository.whatsapp_phone(kode)