/**
 * Stage Inventory: CRUD + Post + Unpost + edge + multi-tenant.
 */
import { login, makeClient } from '../lib/api.js'
import type { CheckResult, Step, Report } from '../lib/report.js'
import { generateEan13 } from '../lib/barcode.js'

type Org = {
  orgId: string; email: string; password: string; token: string
  storeId: string; productId: string; product2Id: string
  currencyId: string; unitKgId: string; groupId: string; priceTypeRetailId: string
}
type Ctx = {
  apiOnly: boolean
  ts: number
  orgA?: Org
  orgB?: Org
  invIdA?: string
  invIdAuto?: string
}
interface StepCtx { ctx: Ctx; step: Step; report: Report }

const TS = Date.now()
function check(step: Step, c: CheckResult) { step.checks.push(c) }
function asString(x: unknown): string {
  if (x == null) return ''
  if (typeof x === 'string') return x
  return JSON.stringify(x)
}

async function bootstrapOrg(suffix: string, phone: string, bc1: number, bc2: number): Promise<Org> {
  const api = makeClient()
  const email = `stage-inv-${suffix}-${TS}@food-market.local`
  const password = 'StageInv12345!'
  // Retry on 429 — rate-limit на signup может задержать
  let r = await api.post('/api/auth/signup', { email, password, organizationName: `Inv ${suffix} ${TS}`, phone, plan: 'start' })
  for (let i = 0; i < 5 && r.status === 429; i++) {
    await new Promise(res => setTimeout(res, 15000))
    r = await api.post('/api/auth/signup', { email, password, organizationName: `Inv ${suffix} ${TS}`, phone, plan: 'start' })
  }
  if (r.status !== 200) throw new Error(`signup ${suffix}: ${r.status} ${JSON.stringify(r.data)}`)
  const sess = await login(email, password)
  const auth = makeClient(sess.accessToken)
  const [stores, units, groups, prices, currencies] = await Promise.all([
    auth.get('/api/catalog/stores'),
    auth.get('/api/catalog/units-of-measure'),
    auth.get('/api/catalog/product-groups'),
    auth.get('/api/catalog/price-types'),
    auth.get('/api/catalog/currencies'),
  ])
  const storeId = stores.data.items.find((s: { isMain: boolean }) => s.isMain).id
  const unitKgId = units.data.items.find((u: { code: string }) => u.code === '166').id
  const groupId = groups.data.items.find((g: { parentId: string | null }) => g.parentId == null).id
  const priceTypeRetailId = prices.data.items.find((p: { isRequired: boolean }) => p.isRequired).id
  const currencyId = currencies.data.items.find((c: { code: string }) => c.code === 'KZT').id
  const p1 = await auth.post('/api/catalog/products', {
    name: `Inv P1 ${suffix} ${TS}`, article: `INV-${suffix}-${TS}-1`,
    unitOfMeasureId: unitKgId, vat: 0, vatEnabled: false, productGroupId: groupId,
    barcodes: [{ code: generateEan13(bc1), type: 1, isPrimary: true }],
    prices: [{ priceTypeId: priceTypeRetailId, amount: 100, currencyId }],
  })
  const p2 = await auth.post('/api/catalog/products', {
    name: `Inv P2 ${suffix} ${TS}`, article: `INV-${suffix}-${TS}-2`,
    unitOfMeasureId: unitKgId, vat: 0, vatEnabled: false, productGroupId: groupId,
    barcodes: [{ code: generateEan13(bc2), type: 1, isPrimary: true }],
    prices: [{ priceTypeId: priceTypeRetailId, amount: 200, currencyId }],
  })
  if (p1.status !== 201 || p2.status !== 201) throw new Error(`products ${suffix}: ${p1.status}/${p2.status}`)
  return {
    orgId: r.data.organizationId, email, password, token: sess.accessToken,
    storeId, productId: p1.data.id, product2Id: p2.data.id,
    currencyId, unitKgId, groupId, priceTypeRetailId,
  }
}

async function getStock(token: string, storeId: string, productId: string): Promise<number> {
  const r = await makeClient(token).get(`/api/inventory/stock?storeId=${storeId}`)
  const row = (r.data?.items ?? []).find((x: { productId: string }) => x.productId === productId)
  return row?.quantity ?? 0
}

// ---------------------------------------------------------------------------

export async function inv01_setup({ ctx, step, report }: StepCtx) {
  ctx.ts = TS
  ctx.orgA = await bootstrapOrg('a', '+77011140001', 41, 42)
  await new Promise(r => setTimeout(r, 900))
  ctx.orgB = await bootstrapOrg('b', '+77011140002', 43, 44)
  const a = ctx.orgA
  const api = makeClient(a.token)
  // qty1=50, qty2=80
  const e1 = await api.post('/api/inventory/enters', {
    date: new Date().toISOString(), storeId: a.storeId, currencyId: a.currencyId,
    lines: [
      { productId: a.productId, quantity: 50, unitCost: 10 },
      { productId: a.product2Id, quantity: 80, unitCost: 20 },
    ],
  })
  const post = await api.post(`/api/inventory/enters/${e1.data.id}/post`, {})
  check(step, { kind: 'api', description: 'Enter posted (initial stock)', ok: post.status === 204, detail: `${post.status}` })
  const s1 = await getStock(a.token, a.storeId, a.productId)
  const s2 = await getStock(a.token, a.storeId, a.product2Id)
  check(step, { kind: 'api', description: 'Stock: p1=50, p2=80', ok: s1 === 50 && s2 === 80, detail: `p1=${s1} p2=${s2}` })
}

// ---------------------------------------------------------------------------

export async function inv02_create_draft_autopopulate({ ctx, step, report }: StepCtx) {
  if (!ctx.orgA) { step.status = 'skip'; return }
  const a = ctx.orgA
  const api = makeClient(a.token)
  const res = await api.post('/api/inventory/inventories', {
    date: new Date().toISOString(), storeId: a.storeId, notes: 'auto', lines: null,
  })
  check(step, { kind: 'api', description: 'POST inventories без Lines → 201', ok: res.status === 201, detail: `${res.status} ${asString(res.data).slice(0, 200)}` })
  if (res.status !== 201) return
  ctx.invIdAuto = res.data.id
  const got = await api.get(`/api/inventory/inventories/${ctx.invIdAuto}`)
  const lines = got.data?.lines ?? []
  const p1 = lines.find((l: { productId: string }) => l.productId === a.productId)
  const p2 = lines.find((l: { productId: string }) => l.productId === a.product2Id)
  check(step, {
    kind: 'api', description: 'Авто-Lines содержат оба товара с bookQty = текущий stock',
    ok: p1?.bookQty === 50 && p2?.bookQty === 80,
    detail: `p1.book=${p1?.bookQty} p2.book=${p2?.bookQty}`,
  })
  check(step, {
    kind: 'api', description: 'Auto: ActualQty=0, Diff=-bookQty',
    ok: p1?.actualQty === 0 && p1?.diff === -50,
    detail: `p1.actual=${p1?.actualQty} p1.diff=${p1?.diff}`,
  })
}

// ---------------------------------------------------------------------------

export async function inv03_create_draft_explicit_lines({ ctx, step, report }: StepCtx) {
  if (!ctx.orgA) { step.status = 'skip'; return }
  const a = ctx.orgA
  const api = makeClient(a.token)
  // p1: ActualQty=48 (диф=-2). p2: ActualQty=85 (диф=+5)
  const res = await api.post('/api/inventory/inventories', {
    date: new Date().toISOString(), storeId: a.storeId, notes: 'explicit',
    lines: [
      { productId: a.productId, actualQty: 48 },
      { productId: a.product2Id, actualQty: 85 },
    ],
  })
  check(step, { kind: 'api', description: 'POST с явными Lines → 201', ok: res.status === 201, detail: `${res.status}` })
  if (res.status !== 201) return
  ctx.invIdA = res.data.id
  const got = await api.get(`/api/inventory/inventories/${ctx.invIdA}`)
  const p1 = got.data?.lines?.find((l: { productId: string }) => l.productId === a.productId)
  const p2 = got.data?.lines?.find((l: { productId: string }) => l.productId === a.product2Id)
  check(step, {
    kind: 'api', description: 'p1: book=50, actual=48, diff=-2',
    ok: p1?.bookQty === 50 && p1?.actualQty === 48 && p1?.diff === -2,
    detail: `book=${p1?.bookQty} actual=${p1?.actualQty} diff=${p1?.diff}`,
  })
  check(step, {
    kind: 'api', description: 'p2: book=80, actual=85, diff=+5',
    ok: p2?.bookQty === 80 && p2?.actualQty === 85 && p2?.diff === 5,
    detail: `book=${p2?.bookQty} actual=${p2?.actualQty} diff=${p2?.diff}`,
  })
}

// ---------------------------------------------------------------------------

export async function inv04_update_actual_quantities({ ctx, step, report }: StepCtx) {
  if (!ctx.orgA || !ctx.invIdA) { step.status = 'skip'; return }
  const a = ctx.orgA
  const api = makeClient(a.token)
  // Изменяем ActualQty
  const upd = await api.put(`/api/inventory/inventories/${ctx.invIdA}`, {
    date: new Date().toISOString(), storeId: a.storeId, notes: 'updated',
    lines: [
      { productId: a.productId, actualQty: 49 },
      { productId: a.product2Id, actualQty: 82 },
    ],
  })
  check(step, {
    kind: 'api', description: 'PUT inventory → 204 (фикс EF8 на nav-collection)',
    ok: upd.status === 204,
    detail: `${upd.status} ${asString(upd.data).slice(0, 200)}`,
  })
  const got = await api.get(`/api/inventory/inventories/${ctx.invIdA}`)
  const p1 = got.data?.lines?.find((l: { productId: string }) => l.productId === a.productId)
  const p2 = got.data?.lines?.find((l: { productId: string }) => l.productId === a.product2Id)
  check(step, {
    kind: 'api', description: 'После PUT: p1.actual=49,diff=-1; p2.actual=82,diff=+2',
    ok: p1?.actualQty === 49 && p1?.diff === -1 && p2?.actualQty === 82 && p2?.diff === 2,
    detail: `p1.actual=${p1?.actualQty} diff=${p1?.diff}; p2.actual=${p2?.actualQty} diff=${p2?.diff}`,
  })
}

// ---------------------------------------------------------------------------

export async function inv05_post_creates_adjustment_movements({ ctx, step, report }: StepCtx) {
  if (!ctx.orgA || !ctx.invIdA) { step.status = 'skip'; return }
  const a = ctx.orgA
  const api = makeClient(a.token)
  const s1Before = await getStock(a.token, a.storeId, a.productId)
  const s2Before = await getStock(a.token, a.storeId, a.product2Id)
  // Сейчас diff=-1 и +2; ждём stock=49 и 82
  const post = await api.post(`/api/inventory/inventories/${ctx.invIdA}/post`, {})
  check(step, { kind: 'api', description: 'POST /post → 204', ok: post.status === 204, detail: `${post.status} ${asString(post.data).slice(0, 200)}` })
  const s1After = await getStock(a.token, a.storeId, a.productId)
  const s2After = await getStock(a.token, a.storeId, a.product2Id)
  check(step, {
    kind: 'api', description: 'p1: stock -=1 (был 50→49)',
    ok: s1After - s1Before === -1, detail: `${s1Before} → ${s1After}`,
  })
  check(step, {
    kind: 'api', description: 'p2: stock +=2 (был 80→82)',
    ok: s2After - s2Before === 2, detail: `${s2Before} → ${s2After}`,
  })
  const movs = await api.get(`/api/inventory/movements?take=200`)
  const ours = (movs.data?.items ?? []).filter((m: { documentId: string }) => m.documentId === ctx.invIdA)
  const types = new Set(ours.map((m: { type: string }) => m.type))
  check(step, {
    kind: 'api', description: 'Создано 2 StockMovement type=InventoryAdjustment',
    ok: ours.length === 2 && types.has('InventoryAdjustment'),
    detail: `count=${ours.length} types=${[...types].join(',')}`,
  })
}

// ---------------------------------------------------------------------------

export async function inv06_unpost({ ctx, step, report }: StepCtx) {
  if (!ctx.orgA || !ctx.invIdA) { step.status = 'skip'; return }
  const a = ctx.orgA
  const api = makeClient(a.token)
  const s1Before = await getStock(a.token, a.storeId, a.productId)
  const s2Before = await getStock(a.token, a.storeId, a.product2Id)
  const unpost = await api.post(`/api/inventory/inventories/${ctx.invIdA}/unpost`, {})
  check(step, { kind: 'api', description: 'POST /unpost → 204', ok: unpost.status === 204, detail: `${unpost.status} ${asString(unpost.data).slice(0, 200)}` })
  const s1After = await getStock(a.token, a.storeId, a.productId)
  const s2After = await getStock(a.token, a.storeId, a.product2Id)
  check(step, {
    kind: 'api', description: 'p1: stock +1 обратно (49→50)',
    ok: s1After - s1Before === 1, detail: `${s1Before} → ${s1After}`,
  })
  check(step, {
    kind: 'api', description: 'p2: stock -2 обратно (82→80)',
    ok: s2After - s2Before === -2, detail: `${s2Before} → ${s2After}`,
  })
  const got = await api.get(`/api/inventory/inventories/${ctx.invIdA}`)
  check(step, {
    kind: 'api', description: 'Status=Draft (0)',
    ok: got.data?.status === 0,
    detail: `status=${got.data?.status}`,
  })
}

// ---------------------------------------------------------------------------

export async function inv07_multi_tenant_isolation({ ctx, step, report }: StepCtx) {
  if (!ctx.orgA || !ctx.orgB || !ctx.invIdA) { step.status = 'skip'; return }
  const apiB = makeClient(ctx.orgB.token)
  const list = await apiB.get('/api/inventory/inventories?pageSize=200')
  const sees = (list.data?.items ?? []).some((x: { id: string }) => x.id === ctx.invIdA)
  check(step, {
    kind: 'api', description: 'Org B не видит inventory org A',
    ok: !sees, detail: `total=${list.data?.total} sees=${sees}`,
  })
  if (sees) report.bug({ step: 'inv07', severity: 'critical', title: 'P0: inventory org A виден из B', detail: `id=${ctx.invIdA}` })
  const get = await apiB.get(`/api/inventory/inventories/${ctx.invIdA}`)
  check(step, { kind: 'api', description: 'GET чужого id → 404', ok: get.status === 404, detail: `${get.status}` })
}

// ---------------------------------------------------------------------------

export async function inv08_csv_import_gap_check({ ctx, step, report }: StepCtx) {
  if (!ctx.orgA || !ctx.invIdA) { step.status = 'skip'; return }
  const a = ctx.orgA
  const api = makeClient(a.token)
  // Пытаемся попасть в типичные CSV-import endpoints
  const candidates = [
    `/api/inventory/inventories/${ctx.invIdA}/import`,
    `/api/inventory/inventories/${ctx.invIdA}/csv`,
    `/api/inventory/inventories/${ctx.invIdA}/import-actual`,
  ]
  const found: string[] = []
  for (const url of candidates) {
    const r = await api.post(url, {})
    if (r.status !== 404) found.push(`${url}=${r.status}`)
  }
  // Не fail step'a — фиксируем как информационный gap, чтобы не делать
  // отсутствие CSV-импорта блокером сценария.
  check(step, {
    kind: 'api', description: 'CSV-import endpoint существует ИЛИ отсутствует (см. gap)',
    ok: true, detail: found.length > 0 ? found.join(', ') : 'not found — см. logic gap',
  })
  if (found.length === 0) {
    report.gap(
      'CSV-импорт фактических количеств в Inventory не реализован. ' +
      'Все строки задаются через JSON-API POST/PUT inventories. ' +
      'Для оператора склада это неудобно: реальная инвентаризация — это сканер штрихкодов или Excel-таблица, ' +
      'а не ручной JSON. Желательно добавить POST /api/inventory/inventories/{id}/import-actual ' +
      'с multipart CSV { barcode|article, actualQty }.',
    )
  }
}
