from datetime import date, datetime
from decimal import Decimal
from typing import Any

from pydantic import BaseModel, Field, model_validator


class SalesListItem(BaseModel):
    kodesi: str
    tanggal_faktur: date | None
    tanggal_jatuh_tempo: date | None
    nama_pelanggan: str | None
    nama_sales: str | None
    status: str | None
    biaya: Decimal | None
    subtotal: Decimal | None
    pajak: Decimal | None
    total: Decimal | None
    tanggal_edit: datetime | None
    kode_divisi: str
    ekspedisi: str | None
    keterangan: str | None
    kode_pelanggan: str
    jumlah_koli: int | None
    kode_perjalanan: str | None
    lama_kirim: int | None
    tutup: bool


class Pagination(BaseModel):
    page: int
    page_size: int
    total: int
    total_pages: int


class SalesListResponse(BaseModel):
    data: list[SalesListItem]
    pagination: Pagination


class SalesDetailItem(BaseModel):
    id: str
    urutan: int | None
    kode_barang: str | None
    nama_barang: str | None
    qty: Decimal | None
    satuan: str | None
    harga_satuan: Decimal | None
    diskon1: Decimal | None
    diskon2: Decimal | None
    diskon3: Decimal | None
    potongan: Decimal | None
    harga_total: Decimal | None
    ppn: str
    pajak_detail: Decimal | None
    konversi: Decimal | None
    hb_konversi: Decimal
    kode_lokasi: str | None


class SalesDetailResponse(SalesListItem):
    kode_sales: str | None
    items: list[SalesDetailItem] = Field(default_factory=list)


class CustomerLookup(BaseModel):
    kode_pelanggan: str
    nama_pelanggan: str | None
    alamat: str | None
    telepon: str | None
    kode_tingkat: str | None
    limit_piutang: Decimal
    piutang: Decimal
    limit_hari: int | None


class CustomerListResponse(BaseModel):
    data: list[CustomerLookup]
    pagination: Pagination


class SalespersonLookup(BaseModel):
    kode_sales: str
    nama_sales: str


class LocationLookup(BaseModel):
    kode_lokasi: str
    kode_divisi: str
    nama_lokasi: str


class ProductLookup(BaseModel):
    kode_barang: str
    nama_barang: str | None
    satuan_default: str | None
    harga_beli: Decimal | None
    harga_jual: Decimal | None
    ppn: str | None
    stok_tersedia: Decimal
    kode_akun_penjualan: str | None
    kode_akun_persediaan: str | None
    kode_akun_hpp: str | None


class ProductListResponse(BaseModel):
    data: list[ProductLookup]
    pagination: Pagination


class ProductUnitLookup(BaseModel):
    satuan: str
    konversi: Decimal
    harga_beli: Decimal
    harga_jual: Decimal
    kode_tingkat: str
    barcode: str | None
    diskon1: Decimal
    diskon2: Decimal
    diskon3: Decimal
    potongan: Decimal


class BarcodeLookup(ProductUnitLookup):
    kode_barang: str
    nama_barang: str | None
    ppn: str | None
    stok_tersedia: Decimal
    kode_akun_penjualan: str | None
    kode_akun_persediaan: str | None
    kode_akun_hpp: str | None


class CustomerCreditInfo(BaseModel):
    kode_pelanggan: str
    nama_pelanggan: str | None
    limit_piutang: Decimal
    piutang: Decimal
    sisa_limit: Decimal
    melewati_limit: bool
    limit_hari: int | None
    kode_tingkat: str | None
    tingkat_harga: str | None


class CustomerPriceHistoryItem(BaseModel):
    kodesi: str
    tanggal_faktur: date | None
    qty: Decimal | None
    satuan: str | None
    harga_satuan: Decimal | None
    diskon1: Decimal | None
    diskon2: Decimal | None
    diskon3: Decimal | None
    potongan: Decimal | None
    harga_total: Decimal | None


class ReceivableDocument(BaseModel):
    kodesi: str
    tanggal_faktur: date | None
    total_faktur: Decimal
    total_retur: Decimal
    total_bayar: Decimal
    sisa_piutang: Decimal


class CustomerReceivableSummary(BaseModel):
    kode_pelanggan: str
    tanggal_mulai: date
    tanggal_selesai: date
    jumlah_faktur: int
    total_faktur: Decimal
    total_retur: Decimal
    total_bayar: Decimal
    sisa_piutang: Decimal
    documents: list[ReceivableDocument] = Field(default_factory=list)


class SalesOrderDraftItem(BaseModel):
    id: str
    urutan: int | None
    kode_barang: str | None
    nama_barang: str | None
    qty: Decimal | None
    satuan: str | None
    harga_satuan: Decimal | None
    diskon1: Decimal | None
    diskon2: Decimal | None
    diskon3: Decimal | None
    potongan: Decimal | None
    harga_total: Decimal | None
    ppn: str
    pajak_detail: Decimal | None
    kode_akun_penjualan: str | None
    kode_akun_hpp: str | None
    kode_akun_persediaan: str | None
    konversi: Decimal
    harga_beli_konversi: Decimal
    kode_lokasi: str | None


class SalesOrderDraft(BaseModel):
    kodeso: str
    tanggal_order: datetime | None
    tanggal_edit: datetime | None
    kode_pelanggan: str | None
    nama_pelanggan: str | None
    status: str | None
    biaya: Decimal
    subtotal: Decimal
    pajak: Decimal
    total: Decimal
    metode_ppn: str | None
    persen_ppn: Decimal | None
    catatan: str | None
    keterangan: str | None
    kodesi: str | None
    sudah_diproses: bool
    tutup: bool
    items: list[SalesOrderDraftItem] = Field(default_factory=list)


class SalesShortageOrderRequest(BaseModel):
    tanggal_order: date
    kode_pelanggan: str = Field(min_length=1, max_length=6)
    kode_divisi: str = Field(min_length=2, max_length=6)
    keterangan: str | None = Field(default=None, max_length=430)
    biaya_lain: Decimal = Field(default=Decimal("0"), ge=0)
    metode_ppn: str = Field(default="TANPA PPN", max_length=20)
    persen_ppn: Decimal = Field(default=Decimal("0"), ge=0, le=100)
    items: list["SalesCommandItem"] = Field(min_length=1)

    @model_validator(mode="after")
    def validate_shortage_order(self) -> "SalesShortageOrderRequest":
        if self.metode_ppn == "TANPA PPN" and self.persen_ppn != 0:
            raise ValueError("persen_ppn harus 0 untuk metode TANPA PPN")
        if not self.kode_divisi[-2:].isdigit():
            raise ValueError("dua karakter terakhir kode_divisi harus angka")
        return self


class SalesShortageOrderItem(BaseModel):
    kode_barang: str
    kode_lokasi: str
    qty_order: Decimal
    satuan: str


class SalesShortageOrderResponse(BaseModel):
    kodeso: str
    status: str
    item_count: int
    items: list[SalesShortageOrderItem]


class SalesCommandItem(BaseModel):
    kode_barang: str = Field(min_length=1, max_length=15)
    qty: Decimal = Field(gt=0, multiple_of=Decimal("1"))
    satuan: str = Field(min_length=1, max_length=50)
    harga_satuan: Decimal = Field(ge=0)
    diskon1: Decimal = Field(default=Decimal("0"), ge=0, le=100)
    diskon2: Decimal = Field(default=Decimal("0"), ge=0, le=100)
    diskon3: Decimal = Field(default=Decimal("0"), ge=0, le=100)
    potongan: Decimal = Field(default=Decimal("0"), ge=0)
    ppn_flag: str = Field(default="N", pattern="^[YN]$")
    kode_akun_penjualan: str | None = Field(default=None, max_length=6)
    kode_akun_hpp: str | None = Field(default=None, max_length=6)
    kode_akun_persediaan: str | None = Field(default=None, max_length=6)
    kode_akun_potongan: str | None = Field(default=None, max_length=6)
    konversi: Decimal = Field(default=Decimal("1"), gt=0)
    harga_beli_konversi: Decimal = Field(default=Decimal("0"), ge=0)
    kode_lokasi: str | None = Field(default=None, max_length=6)


class SalesCommandRequest(BaseModel):
    tanggal_faktur: date
    tanggal_jatuh_tempo: date
    kode_pelanggan: str = Field(min_length=1, max_length=6)
    kode_sales: str = Field(min_length=1, max_length=6)
    kode_divisi: str = Field(min_length=2, max_length=6)
    kode_so: str | None = Field(default=None, max_length=14)
    keterangan: str | None = Field(default=None, max_length=500)
    biaya_lain: Decimal = Field(default=Decimal("0"), ge=0)
    kode_akun_biaya: str | None = Field(default=None, max_length=6)
    kode_akun_pajak: str | None = Field(default=None, max_length=6)
    kode_akun_total: str | None = Field(default=None, max_length=6)
    metode_ppn: str = Field(default="TANPA PPN", max_length=20)
    persen_ppn: Decimal = Field(default=Decimal("0"), ge=0, le=100)
    kode_rute: str = Field(default="BEBAS", max_length=20)
    expected_tanggal_edit: datetime | None = None
    items: list[SalesCommandItem] = Field(min_length=1)

    @model_validator(mode="after")
    def validate_dates_and_tax(self) -> "SalesCommandRequest":
        if self.tanggal_jatuh_tempo < self.tanggal_faktur:
            raise ValueError("tanggal_jatuh_tempo tidak boleh sebelum tanggal_faktur")
        if self.metode_ppn == "TANPA PPN" and self.persen_ppn != 0:
            raise ValueError("persen_ppn harus 0 untuk metode TANPA PPN")
        if not self.kode_divisi[-2:].isdigit():
            raise ValueError("dua karakter terakhir kode_divisi harus angka")
        return self


class SalesCommandResponse(BaseModel):
    kodesi: str
    status: str
    fifo_status: str
    document_exists_before: bool
    tanggal_edit: datetime | None = None


class SalesDeleteResponse(BaseModel):
    kodesi: str
    status: str


class SalesLockResponse(BaseModel):
    kodesi: str
    tutup: bool
    fifo_status: str


class SalesBatchLockRequest(BaseModel):
    tanggal_mulai: date
    tanggal_selesai: date
    kode_divisi: str | None = Field(default=None, max_length=6)

    @model_validator(mode="after")
    def validate_period(self) -> "SalesBatchLockRequest":
        if self.tanggal_mulai > self.tanggal_selesai:
            raise ValueError("tanggal_mulai tidak boleh setelah tanggal_selesai")
        if (self.tanggal_selesai - self.tanggal_mulai).days > 31:
            raise ValueError("periode batch lock maksimal 31 hari")
        return self


class SalesBatchLockResponse(BaseModel):
    locked_count: int
    documents: list[str]


class SalesFifoRepostResponse(BaseModel):
    kodesi: str
    fifo_status: str
    synthetic_layer_created: bool = False


class SalesNumberPreviewResponse(BaseModel):
    periode: str
    kode_divisi: str
    prefix: str
    nomor_preview: str
    sequence: int


class SalesReusableNumberItem(BaseModel):
    kodesi: str
    deleted_at: datetime | None
    deleted_by: str | None


class SalesReusableNumberResponse(BaseModel):
    periode: str
    data: list[SalesReusableNumberItem]


class SalesMissingNumberResponse(BaseModel):
    periode: str
    kode_divisi: str | None
    data: list[str]
    truncated: bool


class SalesFifoShortageItem(BaseModel):
    kodesi: str
    tanggal_faktur: date
    kode_barang: str
    nama_barang: str | None
    kode_lokasi: str
    qty_required: Decimal
    qty_posted: Decimal
    qty_shortage: Decimal
    tutup: bool


class SalesFifoShortageResponse(BaseModel):
    tanggal_mulai: date
    tanggal_selesai: date
    data: list[SalesFifoShortageItem]


class SalesStatusReconcileRequest(BaseModel):
    tanggal_mulai: date
    tanggal_selesai: date
    preview_only: bool = True

    @model_validator(mode="after")
    def validate_period(self) -> "SalesStatusReconcileRequest":
        if self.tanggal_mulai > self.tanggal_selesai:
            raise ValueError("tanggal_mulai tidak boleh setelah tanggal_selesai")
        if (self.tanggal_selesai - self.tanggal_mulai).days > 366:
            raise ValueError("periode reconcile maksimal 366 hari")
        return self


class SalesStatusReconcileItem(BaseModel):
    kodesi: str
    status_sekarang: str
    status_seharusnya: str
    total_faktur: Decimal
    total_retur: Decimal
    total_bayar: Decimal
    sisa: Decimal


class SalesStatusReconcileResponse(BaseModel):
    preview_only: bool
    mismatch_count: int
    updated_count: int
    data: list[SalesStatusReconcileItem]


class SalesExpeditionRequest(BaseModel):
    ekspedisi: str = Field(min_length=1, max_length=100)
    tanggal_ekspedisi: date


class SalesPackageRequest(BaseModel):
    jumlah_koli: int = Field(ge=0)
    tanggal_kemas: date


class SalesOperationalUpdateResponse(BaseModel):
    kodesi: str
    status: str


class SalesHistoryResponse(BaseModel):
    kodesi: str
    audit: list[dict[str, Any]]
    payments: list[dict[str, Any]]
    edits: list[dict[str, Any]]
    final_details: list[dict[str, Any]]


class SalesPrintPayload(BaseModel):
    kodesi: str
    document_type: str
    company: dict[str, Any]
    header: dict[str, Any]
    items: list[dict[str, Any]]


class SalesWhatsAppPayload(BaseModel):
    kodesi: str
    nomor_tujuan: str | None
    message: str
    company: dict[str, Any]
    header: dict[str, Any]
    items: list[dict[str, Any]]


class SalesOrderListItem(BaseModel):
    kodeso: str
    tanggal_created: datetime
    nama_pelanggan: str | None = None
    status: str | None = None
    biaya: Decimal = Decimal("0")
    subtotal: Decimal = Decimal("0")
    pajak: Decimal = Decimal("0")
    total: Decimal = Decimal("0")
    tanggal_edit: datetime | None = None
    kodesi: str | None = None
    catatan: str | None = None
    keterangan: str | None = None
    tutup: bool = False


class SalesOrderListResponse(BaseModel):
    data: list[SalesOrderListItem]
    pagination: Pagination


class SalesOrderItemResponse(BaseModel):
    id: str
    urutan: int | None = None
    kode_barang: str
    nama_barang: str | None = None
    qty: Decimal
    satuan: str
    harga_satuan: Decimal
    diskon1: Decimal = Decimal("0")
    diskon2: Decimal = Decimal("0")
    diskon3: Decimal = Decimal("0")
    potongan: Decimal = Decimal("0")
    harga_total: Decimal = Decimal("0")
    ppn: str = "N"
    pajak_detail: Decimal = Decimal("0")
    kode_akun_penjualan: str | None = None
    kode_akun_hpp: str | None = None
    kode_akun_persediaan: str | None = None
    konversi: Decimal = Decimal("1")
    harga_beli_konversi: Decimal = Decimal("0")
    kode_lokasi: str | None = None


class SalesOrderDetailResponse(BaseModel):
    kodeso: str
    tanggal_created: datetime
    tanggal_edit: datetime | None = None
    kode_pelanggan: str
    nama_pelanggan: str | None = None
    status: str | None = None
    biaya: Decimal = Decimal("0")
    subtotal: Decimal = Decimal("0")
    pajak: Decimal = Decimal("0")
    total: Decimal = Decimal("0")
    metode_ppn: str | None = None
    persen_ppn: Decimal | None = None
    catatan: str | None = None
    keterangan: str | None = None
    kodesi: str | None = None
    sudah_diproses: bool = False
    tutup: bool = False
    items: list[SalesOrderItemResponse] = Field(default_factory=list)


class SalesOrderCommandItem(BaseModel):
    kode_barang: str = Field(min_length=1, max_length=15)
    qty: Decimal = Field(gt=0)
    satuan: str = Field(min_length=1, max_length=50)
    harga_satuan: Decimal = Field(ge=0)
    diskon1: Decimal = Field(default=Decimal("0"), ge=0, le=100)
    diskon2: Decimal = Field(default=Decimal("0"), ge=0, le=100)
    diskon3: Decimal = Field(default=Decimal("0"), ge=0, le=100)
    potongan: Decimal = Field(default=Decimal("0"), ge=0)
    ppn_flag: str = Field(default="N", pattern="^[YN]$")
    kode_akun_penjualan: str | None = Field(default=None, max_length=6)
    kode_akun_hpp: str | None = Field(default=None, max_length=6)
    kode_akun_persediaan: str | None = Field(default=None, max_length=6)
    konversi: Decimal = Field(default=Decimal("1"), gt=0)
    harga_beli_konversi: Decimal = Field(default=Decimal("0"), ge=0)
    kode_lokasi: str | None = Field(default=None, max_length=6)


class SalesOrderCommandRequest(BaseModel):
    tanggal_order: date | None = None
    kode_pelanggan: str = Field(min_length=1, max_length=6)
    kode_divisi: str = Field(default="01", min_length=2, max_length=6)
    keterangan: str | None = Field(default=None, max_length=500)
    catatan: str | None = Field(default=None, max_length=1000)
    biaya_lain: Decimal = Field(default=Decimal("0"), ge=0)
    metode_ppn: str = Field(default="TANPA PPN", max_length=20)
    persen_ppn: Decimal = Field(default=Decimal("0"), ge=0, le=100)
    expected_tanggal_edit: datetime | None = None
    items: list[SalesOrderCommandItem] = Field(min_length=1)

    @model_validator(mode="after")
    def validate_so_command(self) -> "SalesOrderCommandRequest":
        if self.metode_ppn == "TANPA PPN" and self.persen_ppn != 0:
            raise ValueError("persen_ppn harus 0 untuk metode TANPA PPN")
        if not self.kode_divisi[-2:].isdigit():
            raise ValueError("dua karakter terakhir kode_divisi harus angka")
        return self


class SalesOrderCommandResponse(BaseModel):
    kodeso: str
    status: str
    document_exists_before: bool
    tanggal_edit: datetime | None = None


class SalesOrderDeleteResponse(BaseModel):
    kodeso: str
    status: str


class SalesOrderHistoryItem(BaseModel):
    kodepengguna: str
    namapengguna: str
    kodetransaksi: str
    status: str
    jumlahedit: int
    tanggal: datetime


SalesOrderNumberPreviewResponse = SalesNumberPreviewResponse





