from datetime import date
from typing import Annotated

from fastapi import APIRouter, Depends, HTTPException, Path, Query, status
from sqlalchemy.ext.asyncio import AsyncSession

from app.db import get_session
from app.config import get_settings
from app.sales.command_repository import SalesCommandRepository
from app.sales.command_service import (
    ConcurrentUpdateError,
    InvalidReferenceError,
    NonTransactionalTablesError,
    SaleLockedError,
    SaleNotFoundError,
    SalesCommandService,
    SalesOrderLinkError,
    WriteApiDisabledError,
)
from app.sales.fifo_repository import SalesFifoRepository
from app.sales.fifo_service import (
    FifoAuthorizationError,
    FifoDocumentNotFoundError,
    FifoDocumentStateError,
    FifoNonTransactionalTablesError,
    FifoPostingError,
    FifoSyntheticCostError,
    FifoWriteDisabledError,
    SalesFifoService,
)
from app.sales.master_repository import SalesMasterRepository
from app.sales.master_service import (
    AmbiguousBarcodeError,
    SalesMasterService,
)
from app.sales.operational_repository import SalesOperationalRepository
from app.sales.operational_service import (
    InvalidPeriodError,
    OperationalNonTransactionalTablesError,
    OperationalSaleNotFoundError,
    OperationalWriteDisabledError,
    SalesOperationalService,
)
from app.sales.repository import SalesRepository
from app.sales.service import SalesService
from app.sales.schemas import (
    BarcodeLookup,
    CustomerCreditInfo,
    CustomerListResponse,
    CustomerPriceHistoryItem,
    CustomerReceivableSummary,
    LocationLookup,
    ProductListResponse,
    ProductUnitLookup,
    SalesBatchLockRequest,
    SalesBatchLockResponse,
    SalesCommandRequest,
    SalesCommandResponse,
    SalesDeleteResponse,
    SalesDetailResponse,
    SalesListResponse,
    SalesFifoRepostResponse,
    SalesLockResponse,
    SalesFifoShortageResponse,
    SalesExpeditionRequest,
    SalesHistoryResponse,
    SalesMissingNumberResponse,
    SalesNumberPreviewResponse,
    SalesOperationalUpdateResponse,
    SalesPackageRequest,
    SalesPrintPayload,
    SalesReusableNumberResponse,
    SalesStatusReconcileRequest,
    SalesStatusReconcileResponse,
    SalesWhatsAppPayload,
    SalespersonLookup,
)
from app.security import (
    AuditUserCode,
    AuditUserName,
    AuditUserRole,
    CurrentPrincipal,
)


router = APIRouter(prefix="/api/penjualan", tags=["penjualan"])


def get_sales_service(
    session: Annotated[AsyncSession, Depends(get_session)],
) -> SalesService:
    return SalesService(SalesRepository(session))


SalesServiceDependency = Annotated[SalesService, Depends(get_sales_service)]


def get_sales_master_service(
    session: Annotated[AsyncSession, Depends(get_session)],
) -> SalesMasterService:
    return SalesMasterService(SalesMasterRepository(session))


SalesMasterServiceDependency = Annotated[
    SalesMasterService, Depends(get_sales_master_service)
]


def get_sales_command_service(
    session: Annotated[AsyncSession, Depends(get_session)],
) -> SalesCommandService:
    return SalesCommandService(
        SalesCommandRepository(session),
        write_enabled=get_settings().enable_write_api,
    )


SalesCommandServiceDependency = Annotated[
    SalesCommandService, Depends(get_sales_command_service)
]


def get_sales_fifo_service(
    session: Annotated[AsyncSession, Depends(get_session)],
) -> SalesFifoService:
    return SalesFifoService(
        SalesFifoRepository(session),
        write_enabled=get_settings().enable_write_api,
    )


SalesFifoServiceDependency = Annotated[
    SalesFifoService, Depends(get_sales_fifo_service)
]


def get_sales_operational_service(
    session: Annotated[AsyncSession, Depends(get_session)],
) -> SalesOperationalService:
    return SalesOperationalService(
        SalesOperationalRepository(session),
        write_enabled=get_settings().enable_write_api,
    )


SalesOperationalServiceDependency = Annotated[
    SalesOperationalService, Depends(get_sales_operational_service)
]
UserCodeHeader = AuditUserCode
UserNameHeader = AuditUserName
UserRoleHeader = AuditUserRole


def command_http_exception(exc: Exception) -> HTTPException:
    if isinstance(exc, WriteApiDisabledError):
        return HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="Write API belum diaktifkan",
        )
    if isinstance(exc, NonTransactionalTablesError):
        return HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail={
                "message": "Tabel transaksi belum menggunakan InnoDB",
                "tables": exc.tables,
            },
        )
    if isinstance(exc, SaleNotFoundError):
        return HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Dokumen penjualan tidak ditemukan",
        )
    if isinstance(exc, SaleLockedError):
        return HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Dokumen penjualan sudah ditutup",
        )
    if isinstance(exc, ConcurrentUpdateError):
        return HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Dokumen telah diubah pengguna lain; muat ulang data",
        )
    if isinstance(exc, SalesOrderLinkError):
        return HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Order tidak ditemukan, sudah ditutup, atau sudah diproses",
        )
    if isinstance(exc, InvalidReferenceError):
        return HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail={
                "message": "Referensi master tidak valid atau tidak aktif",
                "references": exc.references,
            },
        )
    if isinstance(exc, FifoPostingError):
        return HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=str(exc),
        )
    return HTTPException(
        status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
        detail=str(exc),
    )


@router.get("", response_model=SalesListResponse)
async def list_sales(
    service: SalesServiceDependency,
    principal: CurrentPrincipal,
    month: Annotated[int, Query(ge=1, le=12)] = date.today().month,
    year: Annotated[int, Query(ge=2000, le=2100)] = date.today().year,
    sales_date: Annotated[date | None, Query(alias="date")] = None,
    kodesales: Annotated[str | None, Query(alias="kodesales")] = None,
    kode_sales: Annotated[str | None, Query(alias="kode_sales")] = None,
    belum_tutup_buku: bool = False,
    page: Annotated[int, Query(ge=1)] = 1,
    page_size: Annotated[int, Query(ge=1, le=100)] = 25,
) -> SalesListResponse:
    requested_sales = kodesales or kode_sales
    effective_sales = (
        (principal.kode_sales or principal.user_code)
        if principal.role == "SALES"
        else requested_sales
    )
    return await service.list_sales(
        month=month,
        year=year,
        sales_date=sales_date,
        only_unlocked=belum_tutup_buku,
        kode_sales=effective_sales,
        page=page,
        page_size=page_size,
    )


@router.post("", response_model=SalesCommandResponse, status_code=201)
async def create_sale(
    request: SalesCommandRequest,
    service: SalesCommandServiceDependency,
    principal: CurrentPrincipal,
    user_code: UserCodeHeader,
    user_name: UserNameHeader,
) -> SalesCommandResponse:
    if principal.role == "SALES":
        request.kode_sales = principal.kode_sales or principal.user_code
    try:
        return await service.create(
            request=request, user_code=user_code, user_name=user_name
        )
    except (
        WriteApiDisabledError,
        NonTransactionalTablesError,
        InvalidReferenceError,
        SalesOrderLinkError,
        FifoPostingError,
        ValueError,
    ) as exc:
        raise command_http_exception(exc) from exc


@router.put("/{kodesi}", response_model=SalesCommandResponse)
async def update_sale(
    request: SalesCommandRequest,
    service: SalesCommandServiceDependency,
    principal: CurrentPrincipal,
    user_code: UserCodeHeader,
    user_name: UserNameHeader,
    kodesi: Annotated[str, Path(min_length=1, max_length=14)],
) -> SalesCommandResponse:
    if principal.role == "SALES":
        request.kode_sales = principal.kode_sales or principal.user_code
    try:
        return await service.update(
            kodesi=kodesi,
            request=request,
            user_code=user_code,
            user_name=user_name,
        )
    except (
        WriteApiDisabledError,
        NonTransactionalTablesError,
        InvalidReferenceError,
        SaleNotFoundError,
        SaleLockedError,
        ConcurrentUpdateError,
        SalesOrderLinkError,
        FifoPostingError,
        ValueError,
    ) as exc:
        raise command_http_exception(exc) from exc


def fifo_http_exception(exc: Exception) -> HTTPException:
    if isinstance(exc, FifoWriteDisabledError):
        return HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="Write API belum diaktifkan",
        )
    if isinstance(exc, FifoNonTransactionalTablesError):
        return HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail={
                "message": "Tabel FIFO belum seluruhnya InnoDB",
                "tables": exc.tables,
            },
        )
    if isinstance(exc, FifoAuthorizationError):
        return HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Role tidak diizinkan untuk operasi ini",
        )
    if isinstance(exc, FifoDocumentNotFoundError):
        return HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Dokumen penjualan tidak ditemukan",
        )
    if isinstance(
        exc,
        (FifoDocumentStateError, FifoPostingError, FifoSyntheticCostError),
    ):
        return HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=str(exc),
        )
    return HTTPException(
        status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
        detail=str(exc),
    )


FIFO_EXCEPTIONS = (
    FifoWriteDisabledError,
    FifoNonTransactionalTablesError,
    FifoAuthorizationError,
    FifoDocumentNotFoundError,
    FifoDocumentStateError,
    FifoPostingError,
    FifoSyntheticCostError,
)


@router.post("/{kodesi}/lock", response_model=SalesLockResponse)
async def lock_sale(
    service: SalesFifoServiceDependency,
    user_code: UserCodeHeader,
    user_name: UserNameHeader,
    kodesi: Annotated[str, Path(min_length=1, max_length=14)],
) -> SalesLockResponse:
    try:
        return await service.lock(
            kodesi=kodesi, user_code=user_code, user_name=user_name
        )
    except FIFO_EXCEPTIONS as exc:
        raise fifo_http_exception(exc) from exc


@router.post("/{kodesi}/unlock", response_model=SalesLockResponse)
async def unlock_sale(
    service: SalesFifoServiceDependency,
    user_code: UserCodeHeader,
    user_name: UserNameHeader,
    user_role: UserRoleHeader,
    kodesi: Annotated[str, Path(min_length=1, max_length=14)],
) -> SalesLockResponse:
    try:
        return await service.unlock(
            kodesi=kodesi,
            user_code=user_code,
            user_name=user_name,
            user_role=user_role,
        )
    except FIFO_EXCEPTIONS as exc:
        raise fifo_http_exception(exc) from exc


@router.post("/lock/batch", response_model=SalesBatchLockResponse)
async def batch_lock_sales(
    request: SalesBatchLockRequest,
    service: SalesFifoServiceDependency,
    user_code: UserCodeHeader,
    user_name: UserNameHeader,
) -> SalesBatchLockResponse:
    try:
        return await service.batch_lock(
            request=request, user_code=user_code, user_name=user_name
        )
    except FIFO_EXCEPTIONS as exc:
        raise fifo_http_exception(exc) from exc


@router.post(
    "/{kodesi}/fifo/developer-repost",
    response_model=SalesFifoRepostResponse,
)
async def developer_repost_sale_fifo(
    service: SalesFifoServiceDependency,
    user_code: UserCodeHeader,
    user_name: UserNameHeader,
    user_role: UserRoleHeader,
    kodesi: Annotated[str, Path(min_length=1, max_length=14)],
) -> SalesFifoRepostResponse:
    try:
        return await service.developer_repost(
            kodesi=kodesi,
            user_code=user_code,
            user_name=user_name,
            user_role=user_role,
        )
    except FIFO_EXCEPTIONS as exc:
        raise fifo_http_exception(exc) from exc


@router.delete("/{kodesi}", response_model=SalesDeleteResponse)
async def delete_sale(
    service: SalesCommandServiceDependency,
    user_code: UserCodeHeader,
    user_name: UserNameHeader,
    kodesi: Annotated[str, Path(min_length=1, max_length=14)],
) -> SalesDeleteResponse:
    try:
        return await service.delete(
            kodesi=kodesi, user_code=user_code, user_name=user_name
        )
    except (
        WriteApiDisabledError,
        NonTransactionalTablesError,
        SaleNotFoundError,
        SaleLockedError,
    ) as exc:
        raise command_http_exception(exc) from exc


@router.get("/master/pelanggan", response_model=CustomerListResponse)
async def list_customers(
    service: SalesMasterServiceDependency,
    principal: CurrentPrincipal,
    q: Annotated[str | None, Query(max_length=100)] = None,
    page: Annotated[int, Query(ge=1)] = 1,
    page_size: Annotated[int, Query(ge=1, le=100)] = 25,
) -> CustomerListResponse:
    effective_sales = (
        (principal.kode_sales or principal.user_code)
        if principal.role == "SALES"
        else None
    )
    return await service.list_customers(
        search=q, page=page, page_size=page_size, kode_sales=effective_sales
    )


@router.get("/master/sales", response_model=list[SalespersonLookup])
async def list_salespeople(
    service: SalesMasterServiceDependency,
    principal: CurrentPrincipal,
    q: Annotated[str | None, Query(max_length=100)] = None,
    limit: Annotated[int, Query(ge=1, le=100)] = 100,
) -> list[SalespersonLookup]:
    effective_sales = (
        (principal.kode_sales or principal.user_code)
        if principal.role == "SALES"
        else None
    )
    return await service.list_salespeople(
        search=q, limit=limit, kode_sales=effective_sales
    )


@router.get("/master/lokasi", response_model=list[LocationLookup])
async def list_locations(
    service: SalesMasterServiceDependency,
    q: Annotated[str | None, Query(max_length=100)] = None,
    kode_divisi: Annotated[str | None, Query(max_length=10)] = None,
    limit: Annotated[int, Query(ge=1, le=100)] = 100,
) -> list[LocationLookup]:
    return await service.list_locations(
        search=q, kode_divisi=kode_divisi, limit=limit
    )


@router.get("/master/barang", response_model=ProductListResponse)
async def list_products(
    service: SalesMasterServiceDependency,
    q: Annotated[str | None, Query(max_length=100)] = None,
    kode_lokasi: Annotated[str | None, Query(max_length=20)] = None,
    page: Annotated[int, Query(ge=1)] = 1,
    page_size: Annotated[int, Query(ge=1, le=100)] = 25,
) -> ProductListResponse:
    return await service.list_products(
        search=q,
        kode_lokasi=kode_lokasi,
        page=page,
        page_size=page_size,
    )


@router.get(
    "/master/barang/{kodebarang}/satuan",
    response_model=list[ProductUnitLookup],
)
async def list_product_units(
    service: SalesMasterServiceDependency,
    kodebarang: Annotated[str, Path(min_length=1, max_length=30)],
    kode_tingkat: Annotated[str | None, Query(max_length=20)] = None,
) -> list[ProductUnitLookup]:
    return await service.list_product_units(
        kode_barang=kodebarang, kode_tingkat=kode_tingkat
    )


@router.get("/master/barcode/{barcode}", response_model=BarcodeLookup)
async def resolve_barcode(
    service: SalesMasterServiceDependency,
    barcode: Annotated[str, Path(min_length=1, max_length=100)],
    kode_tingkat: Annotated[str | None, Query(max_length=20)] = None,
    kode_lokasi: Annotated[str | None, Query(max_length=20)] = None,
) -> BarcodeLookup:
    try:
        product = await service.resolve_barcode(
            barcode=barcode,
            kode_tingkat=kode_tingkat,
            kode_lokasi=kode_lokasi,
        )
    except AmbiguousBarcodeError as exc:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Barcode terhubung ke lebih dari satu barang",
        ) from exc
    if product is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Barcode tidak ditemukan",
        )
    return product


@router.get(
    "/pelanggan/{kodepelanggan}/credit-info",
    response_model=CustomerCreditInfo,
)
async def get_customer_credit_info(
    service: SalesMasterServiceDependency,
    principal: CurrentPrincipal,
    kodepelanggan: Annotated[str, Path(min_length=1, max_length=30)],
) -> CustomerCreditInfo:
    effective_sales = (
        (principal.kode_sales or principal.user_code)
        if principal.role == "SALES"
        else None
    )
    credit_info = await service.get_credit_info(
        kodepelanggan, kode_sales=effective_sales
    )
    if credit_info is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Pelanggan aktif tidak ditemukan",
        )
    return credit_info


@router.get(
    "/pelanggan/{kodepelanggan}/barang/{kodebarang}/price-history",
    response_model=list[CustomerPriceHistoryItem],
)
async def get_customer_price_history(
    service: SalesMasterServiceDependency,
    principal: CurrentPrincipal,
    kodepelanggan: Annotated[str, Path(min_length=1, max_length=30)],
    kodebarang: Annotated[str, Path(min_length=1, max_length=30)],
    limit: Annotated[int, Query(ge=1, le=100)] = 20,
) -> list[CustomerPriceHistoryItem]:
    effective_sales = (
        (principal.kode_sales or principal.user_code)
        if principal.role == "SALES"
        else None
    )
    return await service.get_price_history(
        kode_pelanggan=kodepelanggan,
        kode_barang=kodebarang,
        limit=limit,
        kode_sales=effective_sales,
    )


@router.get(
    "/pelanggan/{kodepelanggan}/receivable-summary",
    response_model=CustomerReceivableSummary,
)
async def get_customer_receivable_summary(
    service: SalesMasterServiceDependency,
    principal: CurrentPrincipal,
    kodepelanggan: Annotated[str, Path(min_length=1, max_length=30)],
    tanggal_mulai: date = date.today().replace(day=1),
    tanggal_selesai: date = date.today(),
) -> CustomerReceivableSummary:
    if tanggal_mulai > tanggal_selesai:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail="tanggal_mulai tidak boleh setelah tanggal_selesai",
        )
    effective_sales = (
        (principal.kode_sales or principal.user_code)
        if principal.role == "SALES"
        else None
    )
    return await service.get_receivable_summary(
        kode_pelanggan=kodepelanggan,
        tanggal_mulai=tanggal_mulai,
        tanggal_selesai=tanggal_selesai,
        kode_sales=effective_sales,
    )


@router.get("/number/preview", response_model=SalesNumberPreviewResponse)
async def preview_sales_number(
    service: SalesOperationalServiceDependency,
    periode: Annotated[str, Query(pattern=r"^\d{6}$")],
    kode_divisi: Annotated[str, Query(min_length=2, max_length=6)],
) -> SalesNumberPreviewResponse:
    try:
        return await service.preview_number(
            periode=periode, kode_divisi=kode_divisi
        )
    except InvalidPeriodError as exc:
        raise HTTPException(status_code=422, detail=str(exc)) from exc


@router.get("/number/reusable", response_model=SalesReusableNumberResponse)
async def reusable_sales_numbers(
    service: SalesOperationalServiceDependency,
    periode: Annotated[str, Query(pattern=r"^\d{6}$")],
) -> SalesReusableNumberResponse:
    try:
        return await service.reusable_numbers(periode)
    except InvalidPeriodError as exc:
        raise HTTPException(status_code=422, detail=str(exc)) from exc


@router.get("/number/missing", response_model=SalesMissingNumberResponse)
async def missing_sales_numbers(
    service: SalesOperationalServiceDependency,
    periode: Annotated[str, Query(pattern=r"^\d{6}$")],
    kode_divisi: Annotated[str | None, Query(max_length=6)] = None,
) -> SalesMissingNumberResponse:
    try:
        return await service.missing_numbers(
            periode=periode, kode_divisi=kode_divisi
        )
    except InvalidPeriodError as exc:
        raise HTTPException(status_code=422, detail=str(exc)) from exc


@router.get("/fifo/shortage-report", response_model=SalesFifoShortageResponse)
async def fifo_shortage_report(
    service: SalesOperationalServiceDependency,
    tanggal_mulai: date,
    tanggal_selesai: date,
) -> SalesFifoShortageResponse:
    try:
        return await service.fifo_shortages(
            tanggal_mulai=tanggal_mulai,
            tanggal_selesai=tanggal_selesai,
        )
    except ValueError as exc:
        raise HTTPException(status_code=422, detail=str(exc)) from exc


def operational_http_exception(exc: Exception) -> HTTPException:
    if isinstance(exc, OperationalWriteDisabledError):
        return HTTPException(
            status_code=503, detail="Write API belum diaktifkan"
        )
    if isinstance(exc, OperationalNonTransactionalTablesError):
        return HTTPException(
            status_code=503,
            detail={
                "message": "Tabel operasional belum seluruhnya InnoDB",
                "tables": exc.tables,
            },
        )
    if isinstance(exc, OperationalSaleNotFoundError):
        return HTTPException(
            status_code=404, detail="Dokumen penjualan tidak ditemukan"
        )
    return HTTPException(status_code=422, detail=str(exc))


@router.post(
    "/status/reconcile", response_model=SalesStatusReconcileResponse
)
async def reconcile_sales_status(
    request: SalesStatusReconcileRequest,
    service: SalesOperationalServiceDependency,
    user_code: UserCodeHeader,
    user_name: UserNameHeader,
) -> SalesStatusReconcileResponse:
    try:
        return await service.reconcile_statuses(
            request=request, user_code=user_code, user_name=user_name
        )
    except (
        OperationalWriteDisabledError,
        OperationalNonTransactionalTablesError,
    ) as exc:
        raise operational_http_exception(exc) from exc


@router.patch(
    "/{kodesi}/ekspedisi", response_model=SalesOperationalUpdateResponse
)
async def update_sales_expedition(
    request: SalesExpeditionRequest,
    service: SalesOperationalServiceDependency,
    user_code: UserCodeHeader,
    user_name: UserNameHeader,
    kodesi: Annotated[str, Path(min_length=1, max_length=14)],
) -> SalesOperationalUpdateResponse:
    try:
        return await service.update_expedition(
            kodesi=kodesi,
            request=request,
            user_code=user_code,
            user_name=user_name,
        )
    except (
        OperationalWriteDisabledError,
        OperationalNonTransactionalTablesError,
        OperationalSaleNotFoundError,
    ) as exc:
        raise operational_http_exception(exc) from exc


@router.patch(
    "/{kodesi}/koli", response_model=SalesOperationalUpdateResponse
)
async def update_sales_packages(
    request: SalesPackageRequest,
    service: SalesOperationalServiceDependency,
    user_code: UserCodeHeader,
    user_name: UserNameHeader,
    kodesi: Annotated[str, Path(min_length=1, max_length=14)],
) -> SalesOperationalUpdateResponse:
    try:
        return await service.update_packages(
            kodesi=kodesi,
            request=request,
            user_code=user_code,
            user_name=user_name,
        )
    except (
        OperationalWriteDisabledError,
        OperationalNonTransactionalTablesError,
        OperationalSaleNotFoundError,
    ) as exc:
        raise operational_http_exception(exc) from exc


@router.get("/{kodesi}/history", response_model=SalesHistoryResponse)
async def get_sales_history(
    service: SalesOperationalServiceDependency,
    kodesi: Annotated[str, Path(min_length=1, max_length=14)],
) -> SalesHistoryResponse:
    try:
        return await service.history(kodesi)
    except OperationalSaleNotFoundError as exc:
        raise operational_http_exception(exc) from exc


@router.get("/{kodesi}/print/invoice", response_model=SalesPrintPayload)
async def get_sales_invoice_payload(
    service: SalesOperationalServiceDependency,
    kodesi: Annotated[str, Path(min_length=1, max_length=14)],
) -> SalesPrintPayload:
    try:
        return await service.print_payload(
            kodesi=kodesi, document_type="invoice"
        )
    except OperationalSaleNotFoundError as exc:
        raise operational_http_exception(exc) from exc


@router.get("/{kodesi}/print/surat-jalan", response_model=SalesPrintPayload)
async def get_sales_delivery_note_payload(
    service: SalesOperationalServiceDependency,
    kodesi: Annotated[str, Path(min_length=1, max_length=14)],
) -> SalesPrintPayload:
    try:
        return await service.print_payload(
            kodesi=kodesi, document_type="surat_jalan"
        )
    except OperationalSaleNotFoundError as exc:
        raise operational_http_exception(exc) from exc


@router.get("/{kodesi}/wa-payload", response_model=SalesWhatsAppPayload)
async def get_sales_whatsapp_payload(
    service: SalesOperationalServiceDependency,
    kodesi: Annotated[str, Path(min_length=1, max_length=14)],
) -> SalesWhatsAppPayload:
    try:
        return await service.whatsapp_payload(kodesi)
    except OperationalSaleNotFoundError as exc:
        raise operational_http_exception(exc) from exc


@router.get("/{kodesi}", response_model=SalesDetailResponse)
async def get_sale(
    service: SalesServiceDependency,
    principal: CurrentPrincipal,
    kodesi: Annotated[str, Path(min_length=1, max_length=30)],
) -> SalesDetailResponse:
    effective_sales = (
        (principal.kode_sales or principal.user_code)
        if principal.role == "SALES"
        else None
    )
    sale = await service.get_sale(kodesi, kode_sales=effective_sales)
    if sale is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Dokumen penjualan tidak ditemukan",
        )
    return sale
