import { APIRequestContext, expect, request } from "@playwright/test"
import { env, AUTH_STORAGE_STATE } from "./env"

/**
 * Standalone APIRequestContext for use in `test.beforeAll`, where the test-scoped `request`
 * fixture isn't available. Reuses the same storageState the "setup" project produced, so it
 * is already authenticated. Caller must `.dispose()` it in `afterAll`.
 */
export async function newApiContext(): Promise<APIRequestContext> {
  return request.newContext({ baseURL: env.baseUrl, storageState: AUTH_STORAGE_STATE })
}

// Thin wrapper over the same `/api/v1` surface `Frontend/erp-system/lib/api/*.ts` calls,
// used to seed/verify data directly against the backend so specs don't have to build every
// prerequisite (vendors, POs, templates) by driving the UI. `request` must already carry
// the authenticated session cookie - either via the "setup" project's storageState, or by
// passing a context created after `AuthApi.login`.
const API_BASE = "/api/v1"

export interface Warehouse {
  warehouseId: number
  code: string
  name: string
}

export interface Vendor {
  vendorId: number
  code: string
  name: string
}

export interface Item {
  itemId: number
  sku: string
  name: string
  baseUomId: number
}

export interface Uom {
  uomId: number
  name: string
}

export interface Category {
  categoryId: number
  name: string
}

/** Suffixes every seeded code/SKU with a run-unique token so parallel/rerun specs never collide. */
export function uniqueSuffix(): string {
  return `${Date.now()}${Math.floor(Math.random() * 1000)}`
}

export class ApiSeeder {
  constructor(private readonly request: APIRequestContext) {}

  private async get<T>(path: string): Promise<T> {
    const res = await this.request.get(`${API_BASE}${path}`)
    expect(res.ok(), `GET ${path} -> ${res.status()}: ${await res.text()}`).toBeTruthy()
    return res.json()
  }

  private async post<T>(path: string, data: unknown): Promise<T> {
    const res = await this.request.post(`${API_BASE}${path}`, { data })
    expect(res.ok(), `POST ${path} -> ${res.status()}: ${await res.text()}`).toBeTruthy()
    return res.json()
  }

  // --- reference data (relies on DataSeeder's MAIN/SHOP/PCS/BOX/General Goods seed) -----

  async firstWarehouse(): Promise<Warehouse> {
    const page = await this.get<{ items: Warehouse[] }>("/warehouses?page=1&pageSize=1")
    if (!page.items.length) throw new Error("No warehouses found - expected DataSeeder's MAIN warehouse to exist.")
    return page.items[0]
  }

  async secondWarehouse(): Promise<Warehouse> {
    const page = await this.get<{ items: Warehouse[] }>("/warehouses?page=1&pageSize=10")
    if (page.items.length < 2) throw new Error("Need at least 2 warehouses (DataSeeder seeds MAIN + SHOP).")
    return page.items[1]
  }

  async firstUom(): Promise<Uom> {
    const page = await this.get<{ items: Uom[] }>("/uoms?page=1&pageSize=1")
    if (!page.items.length) throw new Error("No UOMs found - expected DataSeeder's PCS uom to exist.")
    return page.items[0]
  }

  async firstCategory(): Promise<Category> {
    const page = await this.get<{ items: Category[] }>("/categories?page=1&pageSize=1")
    if (!page.items.length) throw new Error("No categories found - expected DataSeeder's General Goods category.")
    return page.items[0]
  }

  // --- writes used to build test fixtures -------------------------------------------

  async createVendor(namePrefix = "E2E Vendor"): Promise<Vendor> {
    const suffix = uniqueSuffix()
    return this.post<Vendor>("/vendors", {
      code: `E2E-V-${suffix}`,
      name: `${namePrefix} ${suffix}`,
      currency: "LKR",
    })
  }

  async createItem(opts: { namePrefix?: string; categoryId?: number; baseUomId?: number } = {}): Promise<Item> {
    const suffix = uniqueSuffix()
    const categoryId = opts.categoryId ?? (await this.firstCategory()).categoryId
    const baseUomId = opts.baseUomId ?? (await this.firstUom()).uomId
    return this.post<Item>("/items", {
      sku: `E2E-SKU-${suffix}`,
      name: `${opts.namePrefix ?? "E2E Item"} ${suffix}`,
      categoryId,
      baseUomId,
      stockNature: "Stocked",
      trackingMode: "None",
    })
  }

  /** Direct (no-PO) GRN, confirmed immediately, so the item has on-hand stock to test against. */
  async receiveStock(opts: { warehouseId: number; vendorId: number; itemId: number; qty: number; unitCost: number }) {
    const grn = await this.post<{ grnId: number }>("/grns", {
      vendorId: opts.vendorId,
      warehouseId: opts.warehouseId,
      lines: [
        {
          itemId: opts.itemId,

          qty: opts.qty,
          unitCost: opts.unitCost,
          discountPct: 0,
          vatPct: 0,
          holdStatus: "Available",
        },
      ],
    })
    await this.post(`/grns/${grn.grnId}/confirm`, {})
    return grn
  }

  /** Direct GRN with the line held for inspection, confirmed - gives the detail page a line with Release/Reject actions. */
  async receiveStockOnHold(opts: { warehouseId: number; vendorId: number; itemId: number; qty: number; unitCost: number }) {
    const grn = await this.post<{ grnId: number; lines: { grnLineId: number }[] }>("/grns", {
      vendorId: opts.vendorId,
      warehouseId: opts.warehouseId,
      lines: [
        {
          itemId: opts.itemId,

          qty: opts.qty,
          unitCost: opts.unitCost,
          discountPct: 0,
          vatPct: 0,
          holdStatus: "OnHold",
        },
      ],
    })
    await this.post(`/grns/${grn.grnId}/confirm`, {})
    return grn
  }

  async createPurchaseOrder(opts: { vendorId: number; warehouseId: number; itemId: number; qty: number; unitPrice: number }) {
    return this.post<{ poId: number; docNo: string }>("/purchase-orders", {
      vendorId: opts.vendorId,
      lines: [
        {
          itemId: opts.itemId,

          warehouseId: opts.warehouseId,
          qty: opts.qty,
          unitPrice: opts.unitPrice,
          tax: 0,
        },
      ],
    })
  }

  /**
   * Minimal single-stage template: one stage that is both entry and terminal, one Stock
   * input (the raw material) and one item-bearing output (the finished good) - the
   * smallest graph ProductionGraphValidator accepts (Backend/ERPCore/Services/Production/
   * ProductionGraphValidator.cs: exactly one terminal, terminal has exactly one item output).
   */
  async createSingleStageTemplate(opts: { rawItemId: number; finishedItemId: number }) {
    const suffix = uniqueSuffix()
    return this.post<{ templateId: number; code: string; name: string }>("/production-templates", {
      code: `E2E-TPL-${suffix}`,
      name: `E2E Template ${suffix}`,
      stages: [
        {
          key: "stage-1",
          name: "Assemble",
          estimatedMinutes: 10,
          posX: 0,
          posY: 0,
          fieldDefs: [],
          // No unit on either row: an input is a pack count unless it says otherwise, and the
          // terminal output takes its unit from the finished item.
          inputs: [{ source: "Stock", itemId: opts.rawItemId, qtyPerBatch: 1 }],
          outputs: [{ key: "out-1", itemId: opts.finishedItemId, name: "Finished good", qtyPerBatch: 1 }],
        },
      ],
      edges: [],
      annotations: [],
    })
  }

  async stockOnHand(itemId: number, warehouseId: number) {
    return this.get<{ onHand: number; available: number }>(`/stock/on-hand?itemId=${itemId}&warehouseId=${warehouseId}`)
  }
}
