// One typed client method per GRN endpoint (docs/11-BACKEND-PHASE1.md §4).
// In-memory mock store (lib/api/mock-data.ts) — no backend API calls. Note
// GET /grns and GET /grns/{id} are not in docs/11-BACKEND-PHASE1.md §4 — see
// the note in Frontend/PROGRESS.md §4.
import { PagedResponse } from "@/types/common"
import {
  ConfirmGrnResponse,
  CreateGrnRequest,
  CreatedLayer,
  Grn,
  GrnStatus,
  GrnSummary,
  ReleaseAction,
  ReleaseGrnLineResponse,
} from "@/types/grn"
import { allocateGrnId, allocateGrnLineId, mockDelay, mockGrns, mockPurchaseOrders, receiveLayer } from "@/lib/api/mock-data"

export interface ListGrnsParams {
  page?: number
  pageSize?: number
  /** Free-text search over doc no. and vendor/PO/warehouse id (docs/11 §1.5). */
  q?: string
  status?: GrnStatus
  poId?: number
  warehouseId?: number
}

function toSummary(grn: Grn): GrnSummary {
  return {
    grnId: grn.grnId,
    docNo: grn.docNo,
    poId: grn.poId,
    vendorId: grn.vendorId,
    warehouseId: grn.warehouseId,
    status: grn.status,
    createdAt: grn.createdAt,
  }
}

export const grnsApi = {
  list(params: ListGrnsParams = {}): Promise<PagedResponse<GrnSummary>> {
    const term = params.q?.trim().toLowerCase()

    const filtered = mockGrns
      .filter((g) => !params.status || g.status === params.status)
      .filter((g) => !params.poId || g.poId === params.poId)
      .filter((g) => !params.warehouseId || g.warehouseId === params.warehouseId)
      .filter((g) => {
        if (!term) return true
        const haystack = [g.docNo, String(g.poId ?? ""), String(g.vendorId), String(g.warehouseId)]
          .join(" ")
          .toLowerCase()
        return haystack.includes(term)
      })
      .map(toSummary)
      .sort((a, b) => b.grnId - a.grnId)

    const page = params.page ?? 1
    const pageSize = params.pageSize ?? 20
    const start = (page - 1) * pageSize
    const items = filtered.slice(start, start + pageSize)
    const totalItems = filtered.length
    const totalPages = pageSize <= 0 ? 0 : Math.ceil(totalItems / pageSize)

    return mockDelay({
      items,
      pagination: { page, pageSize, totalItems, totalPages },
    })
  },

  get(grnId: number): Promise<Grn> {
    const grn = mockGrns.find((g) => g.grnId === grnId)
    if (!grn) return Promise.reject(new Error(`Mock GRN ${grnId} not found`))
    return mockDelay(grn)
  },

  create(request: CreateGrnRequest): Promise<Grn> {
    const grnId = allocateGrnId()
    const referencedPo = request.poId ? mockPurchaseOrders.find((p) => p.poId === request.poId) : undefined
    const grn: Grn = {
      grnId,
      docNo: `GRN-2026-${String(grnId).padStart(5, "0")}`,
      poId: request.poId ?? null,
      // Vendor is derived from the PO when receiving against one (as the real
      // backend does) — request.vendorId is only meaningful for a direct receipt.
      vendorId: referencedPo?.vendorId ?? request.vendorId ?? 0,
      warehouseId: request.warehouseId,
      status: "Draft",
      createdBy: 17,
      createdAt: new Date().toISOString(),
      lines: request.lines.map((line) => ({
        grnLineId: allocateGrnLineId(),
        poLineId: line.poLineId ?? null,
        itemId: line.itemId,
        uomId: line.uomId,
        binId: line.binId ?? null,
        qty: line.qty,
        unitCost: line.unitCost,
        receivedValue: Math.round(line.qty * line.unitCost * 100) / 100,
        holdStatus: line.holdStatus,
        batchId: line.batch ? allocateGrnLineId() : null,
      })),
    }
    mockGrns.push(grn)
    return mockDelay(grn)
  },

  confirm(grnId: number, idempotencyKey?: string): Promise<ConfirmGrnResponse> {
    void idempotencyKey // real backend dedupes on this; the mock always reprocesses
    const grn = mockGrns.find((g) => g.grnId === grnId)
    if (!grn) return Promise.reject(new Error(`Mock GRN ${grnId} not found`))
    if (grn.status === "Confirmed" || grn.status === "Closed") {
      return Promise.reject(new Error(`${grn.docNo} has already been confirmed.`))
    }

    grn.status = "Confirmed"

    const createdLayers: CreatedLayer[] = []
    const ledgerRefs: number[] = []

    for (const line of grn.lines) {
      // FR-GRN-06: each line creates a FIFO layer + posts an inbound ledger entry.
      const { layer, ledger } = receiveLayer({
        itemId: line.itemId,
        warehouseId: grn.warehouseId,
        binId: line.binId,
        batchId: line.batchId,
        grnLineId: line.grnLineId,
        qty: line.qty,
        unitCost: line.unitCost,
        userId: grn.createdBy,
        sourceDocType: "GRN",
        sourceDocId: grn.grnId,
      })
      createdLayers.push({
        layerId: layer.layerId,
        itemId: layer.itemId,
        warehouseId: layer.warehouseId,
        batchId: layer.batchId,
        qtyReceived: layer.qtyReceived,
        qtyRemaining: layer.qtyRemaining,
        unitCost: layer.unitCost,
        receiptDate: layer.receiptDate,
      })
      ledgerRefs.push(ledger.ledgerId)

      // FR-PROC-07: accrue the PO line's received quantity as GRNs confirm.
      if (line.poLineId && grn.poId) {
        const po = mockPurchaseOrders.find((p) => p.poId === grn.poId)
        const poLine = po?.lines.find((l) => l.poLineId === line.poLineId)
        if (poLine) poLine.qtyReceived = Math.min(poLine.qty, poLine.qtyReceived + line.qty)
      }
    }

    let poStatus: string | null = null
    if (grn.poId) {
      const po = mockPurchaseOrders.find((p) => p.poId === grn.poId)
      if (po) {
        const fullyReceived = po.lines.every((l) => l.qtyReceived >= l.qty)
        const anyReceived = po.lines.some((l) => l.qtyReceived > 0)
        po.status = fullyReceived ? "FullyReceived" : anyReceived ? "PartiallyReceived" : po.status
        poStatus = po.status
      }
    }

    const response: ConfirmGrnResponse = {
      grnId: grn.grnId,
      status: grn.status,
      postedAt: new Date().toISOString(),
      createdLayers,
      ledgerRefs,
      poStatus,
    }
    return mockDelay(response)
  },

  releaseLine(grnId: number, grnLineId: number, action: ReleaseAction): Promise<ReleaseGrnLineResponse> {
    const grn = mockGrns.find((g) => g.grnId === grnId)
    const line = grn?.lines.find((l) => l.grnLineId === grnLineId)
    if (!grn || !line) return Promise.reject(new Error(`Mock GRN line ${grnLineId} not found`))

    line.holdStatus = action === "Release" ? "Available" : "Rejected"
    return mockDelay({ grnLineId: line.grnLineId, holdStatus: line.holdStatus })
  },

  // Draft-only — once confirmed, a GRN has created stock layers/ledger entries
  // and is no longer safe to rewrite in place.
  update(grnId: number, request: CreateGrnRequest): Promise<Grn> {
    const grn = mockGrns.find((g) => g.grnId === grnId)
    if (!grn) return Promise.reject(new Error(`Mock GRN ${grnId} not found`))
    if (grn.status !== "Draft") {
      return Promise.reject(new Error(`${grn.docNo} is ${grn.status.toLowerCase()} and can no longer be edited.`))
    }

    const referencedPo = request.poId ? mockPurchaseOrders.find((p) => p.poId === request.poId) : undefined
    grn.poId = request.poId ?? null
    grn.vendorId = referencedPo?.vendorId ?? request.vendorId ?? grn.vendorId
    grn.warehouseId = request.warehouseId
    grn.lines = request.lines.map((line) => ({
      grnLineId: allocateGrnLineId(),
      poLineId: line.poLineId ?? null,
      itemId: line.itemId,
      uomId: line.uomId,
      binId: line.binId ?? null,
      qty: line.qty,
      unitCost: line.unitCost,
      receivedValue: Math.round(line.qty * line.unitCost * 100) / 100,
      holdStatus: line.holdStatus,
      batchId: line.batch ? allocateGrnLineId() : null,
    }))
    return mockDelay(grn)
  },

  remove(grnId: number): Promise<void> {
    const grn = mockGrns.find((g) => g.grnId === grnId)
    if (!grn) return Promise.reject(new Error(`Mock GRN ${grnId} not found`))
    if (grn.status !== "Draft") {
      return Promise.reject(new Error(`${grn.docNo} is ${grn.status.toLowerCase()} and can no longer be deleted.`))
    }
    mockGrns.splice(mockGrns.indexOf(grn), 1)
    return mockDelay(undefined)
  },
}
