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.config import get_settings
from app.db import get_session
from app.sales.master_repository import SalesMasterRepository
from app.sales.master_service import (
    AmbiguousBarcodeError,
    SalesMasterService,
)
from app.sales.order_repository import SalesOrderRepository
from app.sales.order_service import (
    InvalidOrderReferenceError,
    NoStockShortageError,
    OrderAlreadyProcessedError,
    OrderConcurrentUpdateError,
    OrderCreditLimitExceededError,
    OrderLockedError,
    OrderNonTransactionalTablesError,
    OrderNotFoundError,
    OrderPeriodClosedError,
    OrderWriteDisabledError,
    SalesOrderService,
)
from app.sales.schemas import (
    BarcodeLookup,
    CustomerCreditInfo,
    CustomerListResponse,
    CustomerReceivableSummary,
    LocationLookup,
    ProductListResponse,
    ProductUnitLookup,
    SalesOrderDetailResponse,
    SalesOrderDraft,
    SalesOrderHistoryItem,
    SalesOrderListResponse,
    SalesOrderCommandRequest,
    SalesOrderCommandResponse,
    SalesOrderDeleteResponse,
    SalesOrderNumberPreviewResponse,
    SalesShortageOrderRequest,
    SalesShortageOrderResponse,
    SalespersonLookup,
)
from app.security import AuditUserCode, AuditUserName, CurrentPrincipal


router = APIRouter(prefix="/api/order-penjualan", tags=["order-penjualan"])


def get_sales_order_service(
    session: Annotated[AsyncSession, Depends(get_session)],
) -> SalesOrderService:
    settings = get_settings()
    return SalesOrderService(
        SalesOrderRepository(session),
        write_enabled=settings.enable_order_write_api or settings.enable_write_api,
    )


SalesOrderServiceDependency = Annotated[
    SalesOrderService, Depends(get_sales_order_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)
]

UserCodeHeader = AuditUserCode
UserNameHeader = AuditUserName


def order_command_http_exception(exc: Exception) -> HTTPException:
    if isinstance(exc, OrderWriteDisabledError):
        return HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="Write API belum diaktifkan",
        )
    if isinstance(exc, OrderNonTransactionalTablesError):
        return HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail={
                "message": "Tabel order belum seluruhnya InnoDB",
                "tables": exc.tables,
            },
        )
    if isinstance(exc, OrderNotFoundError):
        return HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Order penjualan tidak ditemukan",
        )
    if isinstance(exc, OrderLockedError):
        return HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Dokumen order penjualan sudah ditutup",
        )
    if isinstance(exc, OrderAlreadyProcessedError):
        detail = (
            f"Order penjualan sudah diproses ke faktur penjualan ({exc.kodesi})"
            if exc.kodesi
            else "Order penjualan sudah diproses ke faktur penjualan"
        )
        return HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=detail,
        )
    if isinstance(exc, OrderPeriodClosedError):
        return HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail="Periode transaksi sudah ditutup buku",
        )
    if isinstance(exc, OrderCreditLimitExceededError):
        return HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail={
                "message": "Total order melebihi limit piutang pelanggan",
                "limit": str(exc.limit),
                "current_piutang": str(exc.current_piutang),
                "requested_total": str(exc.requested_total),
            },
        )
    if isinstance(exc, OrderConcurrentUpdateError):
        return HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Data order sudah diubah oleh pengguna lain, silakan refresh",
        )
    if isinstance(exc, InvalidOrderReferenceError):
        return HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail={
                "message": "Referensi master tidak valid atau tidak aktif",
                "references": exc.references,
            },
        )
    if isinstance(exc, NoStockShortageError):
        return HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Tidak ada kekurangan stok untuk dibuatkan order",
        )
    return HTTPException(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        detail=str(exc),
    )


# --- List Order Penjualan ---
@router.get("", response_model=SalesOrderListResponse)
async def list_orders(
    service: SalesOrderServiceDependency,
    principal: CurrentPrincipal,
    month: int = Query(
        default_factory=lambda: date.today().month, ge=1, le=12
    ),
    year: int = Query(
        default_factory=lambda: date.today().year, ge=2000, le=2100
    ),
    search: str | None = Query(default=None, max_length=100),
    kodesales: str | None = Query(default=None, max_length=50),
    kode_sales: str | None = Query(default=None, max_length=50),
    belum_tutup_buku: bool = False,
    page: int = Query(default=1, ge=1),
    page_size: int = Query(default=25, ge=1, le=100),
) -> SalesOrderListResponse:
    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_orders(
        month=month,
        year=year,
        search=search,
        only_unlocked=belum_tutup_buku,
        kode_sales=effective_sales,
        page=page,
        page_size=page_size,
    )


# --- Preview Nomor SO ---
@router.get(
    "/number/preview", response_model=SalesOrderNumberPreviewResponse
)
async def preview_order_number(
    service: SalesOrderServiceDependency,
    periode: str = Query(
        default_factory=lambda: date.today().strftime("%y%m"),
        pattern=r"^\d{4}$",
    ),
    kode_divisi: str = Query(default="01", pattern=r"^\d{2}$"),
) -> SalesOrderNumberPreviewResponse:
    return await service.preview_number(period=periode, division=kode_divisi)


# --- Auto Order Shortage ---
@router.post(
    "/auto-from-shortage", response_model=SalesShortageOrderResponse
)
async def create_order_from_shortage(
    request: SalesShortageOrderRequest,
    service: SalesOrderServiceDependency,
    user_code: UserCodeHeader,
    user_name: UserNameHeader,
) -> SalesShortageOrderResponse:
    try:
        return await service.create_from_shortage(
            request=request, user_code=user_code, user_name=user_name
        )
    except Exception as exc:
        raise order_command_http_exception(exc) from exc


# --- Master Lookups ---
@router.get("/master/pelanggan", response_model=CustomerListResponse)
async def lookup_customers(
    service: SalesMasterServiceDependency,
    principal: CurrentPrincipal,
    q: str | None = Query(default=None, max_length=100),
    page: int = Query(default=1, ge=1),
    page_size: int = Query(default=25, ge=1, le=100),
) -> 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 lookup_salespeople(
    service: SalesMasterServiceDependency,
    principal: CurrentPrincipal,
    q: str | None = Query(default=None, max_length=100),
    limit: int = Query(default=20, ge=1, le=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 lookup_locations(
    service: SalesMasterServiceDependency,
    kode_divisi: str | None = Query(default=None, max_length=6),
) -> list[LocationLookup]:
    return await service.list_locations(division_code=kode_divisi)


@router.get("/master/barang", response_model=ProductListResponse)
async def lookup_products(
    service: SalesMasterServiceDependency,
    q: str | None = Query(default=None, max_length=100),
    kode_lokasi: str | None = Query(default=None, max_length=6),
    page: int = Query(default=1, ge=1),
    page_size: int = Query(default=25, ge=1, le=100),
) -> ProductListResponse:
    return await service.list_products(
        search=q,
        location_code=kode_lokasi,
        page=page,
        page_size=page_size,
    )


@router.get(
    "/master/barang/{kodebarang}/satuan",
    response_model=list[ProductUnitLookup],
)
async def lookup_product_units(
    service: SalesMasterServiceDependency,
    kodebarang: Annotated[str, Path(min_length=1, max_length=15)],
    kode_tingkat: str | None = Query(default=None, max_length=6),
) -> list[ProductUnitLookup]:
    return await service.list_units(
        product_code=kodebarang, price_level=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=50)],
    kode_tingkat: str | None = Query(default=None, max_length=6),
    kode_lokasi: str | None = Query(default=None, max_length=6),
) -> BarcodeLookup:
    try:
        result = await service.resolve_barcode(
            barcode=barcode,
            price_level=kode_tingkat,
            location_code=kode_lokasi,
        )
    except AmbiguousBarcodeError as exc:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Barcode lama terhubung ke lebih dari satu barang",
        ) from exc
    if result is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Barcode tidak ditemukan",
        )
    return result


@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=6)],
) -> CustomerCreditInfo:
    effective_sales = (
        (principal.kode_sales or principal.user_code)
        if principal.role == "SALES"
        else None
    )
    if hasattr(service, "customer_credit_info"):
        info = await service.customer_credit_info(
            kodepelanggan, kode_sales=effective_sales
        )
    else:
        info = await service.get_credit_info(
            kodepelanggan, kode_sales=effective_sales
        )
    if info is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Pelanggan tidak ditemukan",
        )
    return info


@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=6)],
    tanggal_mulai: date | None = None,
    tanggal_selesai: date | None = None,
) -> CustomerReceivableSummary:
    effective_sales = (
        (principal.kode_sales or principal.user_code)
        if principal.role == "SALES"
        else None
    )
    if hasattr(service, "customer_receivable_summary"):
        return await service.customer_receivable_summary(
            customer_code=kodepelanggan,
            start_date=tanggal_mulai,
            end_date=tanggal_selesai,
            kode_sales=effective_sales,
        )
    return await service.get_receivable_summary(
        kode_pelanggan=kodepelanggan,
        tanggal_mulai=tanggal_mulai or date.today().replace(day=1),
        tanggal_selesai=tanggal_selesai or date.today(),
        kode_sales=effective_sales,
    )


# --- Create Order Penjualan ---
@router.post(
    "",
    response_model=SalesOrderCommandResponse,
    status_code=status.HTTP_201_CREATED,
)
async def create_order(
    request: SalesOrderCommandRequest,
    service: SalesOrderServiceDependency,
    user_code: UserCodeHeader,
    user_name: UserNameHeader,
) -> SalesOrderCommandResponse:
    try:
        return await service.create_order(
            request=request, user_code=user_code, user_name=user_name
        )
    except Exception as exc:
        raise order_command_http_exception(exc) from exc


# --- Bridge for Penjualan ---
@router.get("/{kodeso}/for-penjualan", response_model=SalesOrderDraft)
async def get_order_for_sales(
    service: SalesOrderServiceDependency,
    principal: CurrentPrincipal,
    kodeso: Annotated[str, Path(min_length=1, max_length=30)],
) -> SalesOrderDraft:
    effective_sales = (
        (principal.kode_sales or principal.user_code)
        if principal.role == "SALES"
        else None
    )
    order = await service.get_for_sales(kodeso, kode_sales=effective_sales)
    if order is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Order penjualan tidak ditemukan",
        )
    return order


# --- History Audit Order Penjualan ---
@router.get(
    "/{kodeso}/history", response_model=list[SalesOrderHistoryItem]
)
async def get_order_history(
    service: SalesOrderServiceDependency,
    principal: CurrentPrincipal,
    kodeso: Annotated[str, Path(min_length=1, max_length=30)],
) -> list[SalesOrderHistoryItem]:
    effective_sales = (
        (principal.kode_sales or principal.user_code)
        if principal.role == "SALES"
        else None
    )
    if effective_sales:
        existing = await service.get_for_sales(kodeso, kode_sales=effective_sales)
        if existing is None:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Order penjualan tidak ditemukan",
            )
    return await service.get_history(kodeso)


# --- Detail Single Order Penjualan ---
@router.get("/{kodeso}", response_model=SalesOrderDetailResponse)
async def get_order_detail(
    service: SalesOrderServiceDependency,
    principal: CurrentPrincipal,
    kodeso: Annotated[str, Path(min_length=1, max_length=30)],
) -> SalesOrderDetailResponse:
    effective_sales = (
        (principal.kode_sales or principal.user_code)
        if principal.role == "SALES"
        else None
    )
    order = await service.get_order(kodeso, kode_sales=effective_sales)
    if order is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Order penjualan tidak ditemukan",
        )
    return order


# --- Update Order Penjualan ---
@router.put("/{kodeso}", response_model=SalesOrderCommandResponse)
async def update_order(
    request: SalesOrderCommandRequest,
    service: SalesOrderServiceDependency,
    user_code: UserCodeHeader,
    user_name: UserNameHeader,
    kodeso: Annotated[str, Path(min_length=1, max_length=30)],
) -> SalesOrderCommandResponse:
    try:
        return await service.update_order(
            kodeso=kodeso,
            request=request,
            user_code=user_code,
            user_name=user_name,
        )
    except Exception as exc:
        raise order_command_http_exception(exc) from exc


# --- Delete Order Penjualan ---
@router.delete("/{kodeso}", response_model=SalesOrderDeleteResponse)
async def delete_order(
    service: SalesOrderServiceDependency,
    user_code: UserCodeHeader,
    user_name: UserNameHeader,
    kodeso: Annotated[str, Path(min_length=1, max_length=30)],
) -> SalesOrderDeleteResponse:
    try:
        return await service.delete_order(
            kodeso=kodeso, user_code=user_code, user_name=user_name
        )
    except Exception as exc:
        raise order_command_http_exception(exc) from exc
