// One typed client method per Purchase Order endpoint (docs/11-BACKEND-PHASE1.md §3.3,
// FR-PROC-03..07). `get`/`list` also back the GRN "against a PO" picker.
// In-memory sample data (lib/api/mock-data.ts) — no backend API calls.
import { ApiResult, PagedResponse } from "@/types/common"
import {
  CancelPurchaseOrderRequest,
  CreatePurchaseOrderRequest,
  PurchaseOrder,
  PurchaseOrderStatus,
  PurchaseOrderSummary,
  UpdatePurchaseOrderRequest,
} from "@/types/procurement"
import {
  allocatePoId,
  bumpPoVersion,
  getPoVersion,
  initPoVersion,
  mockDelay,
  mockPurchaseOrders,
} from "@/lib/api/mock-data"

export interface ListPurchaseOrdersParams {
  page?: number
  pageSize?: number
  q?: string
  status?: PurchaseOrderStatus
  vendorId?: number
}

/** FR-PROC-05 (Option B): freely editable while open — not once fully received/closed/cancelled. */
export function isPoEditable(status: PurchaseOrderStatus): boolean {
  return status !== "FullyReceived" && status !== "Closed" && status !== "Cancelled"
}

function computeTotals(lines: CreatePurchaseOrderRequest["lines"], currency = "LKR") {
  const subTotal = Math.round(lines.reduce((sum, l) => sum + l.qty * l.unitPrice, 0) * 100) / 100
  const tax = Math.round(lines.reduce((sum, l) => sum + l.qty * l.unitPrice * l.tax, 0) * 100) / 100
  return { subTotal, tax, grandTotal: Math.round((subTotal + tax) * 100) / 100, currency }
}

export const purchaseOrdersApi = {
  list(params: ListPurchaseOrdersParams = {}): Promise<PagedResponse<PurchaseOrderSummary>> {
    const term = params.q?.trim().toLowerCase()
    const filtered = mockPurchaseOrders
      .filter((po) => !params.status || po.status === params.status)
      .filter((po) => !params.vendorId || po.vendorId === params.vendorId)
      .filter((po) => !term || `${po.docNo} ${po.vendorId}`.toLowerCase().includes(term))
      .sort((a, b) => b.poId - a.poId)
      .map(
        (po): PurchaseOrderSummary => ({
          poId: po.poId,
          docNo: po.docNo,
          vendorId: po.vendorId,
          status: po.status,
          approvalRequired: po.approvalRequired,
          createdAt: po.createdAt,
          totals: po.totals,
        })
      )

    const page = params.page ?? 1
    const pageSize = params.pageSize ?? 20
    const start = (page - 1) * pageSize
    const items = filtered.slice(start, start + pageSize)

    return mockDelay({
      items,
      pagination: { page, pageSize, totalItems: filtered.length, totalPages: Math.ceil(filtered.length / pageSize) || 0 },
    })
  },

  get(poId: number): Promise<PurchaseOrder> {
    const po = mockPurchaseOrders.find((p) => p.poId === poId)
    if (!po) return Promise.reject(new Error(`Mock purchase order ${poId} not found`))
    return mockDelay(po)
  },

  getWithETag(poId: number): Promise<ApiResult<PurchaseOrder>> {
    const po = mockPurchaseOrders.find((p) => p.poId === poId)
    if (!po) return Promise.reject(new Error(`Mock purchase order ${poId} not found`))
    return mockDelay({ data: po, etag: String(getPoVersion(poId)) })
  },

  create(request: CreatePurchaseOrderRequest): Promise<ApiResult<PurchaseOrder>> {
    if (request.lines.length === 0) return Promise.reject(new Error("Add at least one line."))
    const poId = allocatePoId()
    // FR-PROC-04: approvalRequired defaults false → auto-approved on creation.
    const po: PurchaseOrder = {
      poId,
      docNo: `PO-2026-${String(poId).padStart(5, "0")}`,
      vendorId: request.vendorId,
      requisitionId: request.requisitionId ?? null,
      status: "Approved",
      approvalRequired: false,
      createdBy: 17,
      createdAt: new Date().toISOString(),
      updatedAt: null,
      totals: computeTotals(request.lines),
      lines: request.lines.map((l, i) => ({
        poLineId: 900 + poId * 10 + i,
        itemId: l.itemId,
        uomId: l.uomId,
        warehouseId: l.warehouseId,
        qty: l.qty,
        unitPrice: l.unitPrice,
        tax: l.tax,
        qtyReceived: 0,
      })),
    }
    mockPurchaseOrders.push(po)
    initPoVersion(poId)
    return mockDelay({ data: po, etag: "1" })
  },

  update(poId: number, request: UpdatePurchaseOrderRequest, ifMatch: string): Promise<ApiResult<PurchaseOrder>> {
    const po = mockPurchaseOrders.find((p) => p.poId === poId)
    if (!po) return Promise.reject(new Error(`Mock purchase order ${poId} not found`))
    if (!isPoEditable(po.status)) {
      return Promise.reject(Object.assign(new Error(`${po.docNo} is ${po.status} and can no longer be edited.`), { code: "PO_NOT_EDITABLE" }))
    }
    if (String(getPoVersion(poId)) !== ifMatch) {
      return Promise.reject(Object.assign(new Error("The purchase order was modified by another request."), { code: "CONCURRENCY_CONFLICT" }))
    }
    if (request.lines.length === 0) return Promise.reject(new Error("Add at least one line."))

    const priorQtyReceived = new Map(po.lines.map((l) => [l.poLineId, l.qtyReceived]))
    po.vendorId = request.vendorId
    po.requisitionId = request.requisitionId ?? null
    po.totals = computeTotals(request.lines, po.totals.currency)
    po.lines = request.lines.map((l, i) => {
      // Preserve qtyReceived for lines that already existed (edit-while-open must not erase receipt progress).
      const existingLineId = po.lines[i]?.poLineId
      return {
        poLineId: existingLineId ?? 900 + poId * 10 + i,
        itemId: l.itemId,
        uomId: l.uomId,
        warehouseId: l.warehouseId,
        qty: l.qty,
        unitPrice: l.unitPrice,
        tax: l.tax,
        qtyReceived: existingLineId ? (priorQtyReceived.get(existingLineId) ?? 0) : 0,
      }
    })
    po.updatedAt = new Date().toISOString()

    const next = bumpPoVersion(poId)
    return mockDelay({ data: po, etag: String(next) })
  },

  cancel(poId: number, request: CancelPurchaseOrderRequest): Promise<PurchaseOrder> {
    const po = mockPurchaseOrders.find((p) => p.poId === poId)
    if (!po) return Promise.reject(new Error(`Mock purchase order ${poId} not found`))
    if (po.lines.some((l) => l.qtyReceived > 0)) {
      return Promise.reject(new Error(`${po.docNo} has receipts against it and can no longer be cancelled.`))
    }
    if (!request.reason.trim()) return Promise.reject(new Error("A cancellation reason is required."))
    po.status = "Cancelled"
    po.updatedAt = new Date().toISOString()
    bumpPoVersion(poId)
    return mockDelay(po)
  },
}
