from datetime import date

from app.sales.master_repository import SalesMasterRepository
from app.sales.schemas import (
    BarcodeLookup,
    CustomerCreditInfo,
    CustomerListResponse,
    CustomerLookup,
    CustomerPriceHistoryItem,
    CustomerReceivableSummary,
    LocationLookup,
    Pagination,
    ProductListResponse,
    ProductLookup,
    ProductUnitLookup,
    SalespersonLookup,
)


class AmbiguousBarcodeError(Exception):
    pass


class SalesMasterService:
    def __init__(self, repository: SalesMasterRepository) -> None:
        self._repository = repository

    @staticmethod
    def _pagination(*, page: int, page_size: int, total: int) -> Pagination:
        return Pagination(
            page=page,
            page_size=page_size,
            total=total,
            total_pages=(total + page_size - 1) // page_size,
        )

    async def list_customers(
        self,
        *,
        search: str | None,
        page: int,
        page_size: int,
        kode_sales: str | None = None,
    ) -> CustomerListResponse:
        rows, total = await self._repository.list_customers(
            search=search,
            kode_sales=kode_sales,
            limit=page_size,
            offset=(page - 1) * page_size,
        )
        return CustomerListResponse(
            data=[CustomerLookup.model_validate(row) for row in rows],
            pagination=self._pagination(
                page=page, page_size=page_size, total=total
            ),
        )

    async def list_salespeople(
        self, *, search: str | None, limit: int, kode_sales: str | None = None
    ) -> list[SalespersonLookup]:
        rows = await self._repository.list_salespeople(
            search=search, limit=limit, kode_sales=kode_sales
        )
        return [SalespersonLookup.model_validate(row) for row in rows]

    async def list_locations(
        self, *, search: str | None, kode_divisi: str | None, limit: int
    ) -> list[LocationLookup]:
        rows = await self._repository.list_locations(
            search=search, kode_divisi=kode_divisi, limit=limit
        )
        return [LocationLookup.model_validate(row) for row in rows]

    async def list_products(
        self,
        *,
        search: str | None,
        kode_lokasi: str | None,
        page: int,
        page_size: int,
    ) -> ProductListResponse:
        rows, total = await self._repository.list_products(
            search=search,
            kode_lokasi=kode_lokasi,
            limit=page_size,
            offset=(page - 1) * page_size,
        )
        return ProductListResponse(
            data=[ProductLookup.model_validate(row) for row in rows],
            pagination=self._pagination(
                page=page, page_size=page_size, total=total
            ),
        )

    async def list_product_units(
        self, *, kode_barang: str, kode_tingkat: str | None
    ) -> list[ProductUnitLookup]:
        rows = await self._repository.list_product_units(
            kode_barang=kode_barang, kode_tingkat=kode_tingkat
        )
        return [ProductUnitLookup.model_validate(row) for row in rows]

    async def resolve_barcode(
        self,
        *,
        barcode: str,
        kode_tingkat: str | None,
        kode_lokasi: str | None,
    ) -> BarcodeLookup | None:
        rows = await self._repository.find_barcode(
            barcode=barcode,
            kode_tingkat=kode_tingkat,
            kode_lokasi=kode_lokasi,
        )
        product_codes = {row["kode_barang"] for row in rows}
        if len(product_codes) > 1:
            raise AmbiguousBarcodeError(barcode)
        return BarcodeLookup.model_validate(rows[0]) if rows else None

    async def get_credit_info(
        self, kode_pelanggan: str, *, kode_sales: str | None = None
    ) -> CustomerCreditInfo | None:
        row = await self._repository.get_credit_info(
            kode_pelanggan, kode_sales=kode_sales
        )
        return CustomerCreditInfo.model_validate(row) if row else None

    async def get_price_history(
        self,
        *,
        kode_pelanggan: str,
        kode_barang: str,
        limit: int,
        kode_sales: str | None = None,
    ) -> list[CustomerPriceHistoryItem]:
        rows = await self._repository.get_price_history(
            kode_pelanggan=kode_pelanggan,
            kode_barang=kode_barang,
            limit=limit,
            kode_sales=kode_sales,
        )
        return [CustomerPriceHistoryItem.model_validate(row) for row in rows]

    async def get_receivable_summary(
        self,
        *,
        kode_pelanggan: str,
        tanggal_mulai: date,
        tanggal_selesai: date,
        kode_sales: str | None = None,
    ) -> CustomerReceivableSummary:
        summary, documents = await self._repository.get_receivable_summary(
            kode_pelanggan=kode_pelanggan,
            tanggal_mulai=tanggal_mulai,
            tanggal_selesai=tanggal_selesai,
            kode_sales=kode_sales,
        )
        return CustomerReceivableSummary.model_validate(
            {
                "kode_pelanggan": kode_pelanggan,
                "tanggal_mulai": tanggal_mulai,
                "tanggal_selesai": tanggal_selesai,
                **summary,
                "documents": documents,
            }
        )

    async def customer_credit_info(
        self, kode_pelanggan: str, *, kode_sales: str | None = None
    ) -> CustomerCreditInfo | None:
        return await self.get_credit_info(kode_pelanggan, kode_sales=kode_sales)

    async def customer_receivable_summary(
        self,
        *,
        customer_code: str,
        start_date: date | None = None,
        end_date: date | None = None,
        kode_sales: str | None = None,
    ) -> CustomerReceivableSummary:
        return await self.get_receivable_summary(
            kode_pelanggan=customer_code,
            tanggal_mulai=start_date or date.today().replace(day=1),
            tanggal_selesai=end_date or date.today(),
            kode_sales=kode_sales,
        )

