/**
 * Stage POS Sync API: /sync + /sales с idempotency.
 */
import { randomUUID } from 'node:crypto'
import { login, makeClient } from '../lib/api.js'
import type { CheckResult, Step, Report } from '../lib/report.js'
import { generateEan13 } from '../lib/barcode.js'

type Ctx = {
  apiOnly: boolean
  ts: number
  email?: string
  password?: string
  token?: string
  storeId?: string
  productId?: string
  serverTime?: string
  idempotencyKey1?: string
  clientSaleId1?: string
  serverSaleId1?: 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 bootstrap(ctx: Ctx): Promise<void> {
  if (ctx.token) return
  const api = makeClient()
  ctx.email = `stage-pos-${TS}@food-market.local`
  ctx.password = 'StagePos12345!'
  let r = await api.post('/api/auth/signup', {
    email: ctx.email, password: ctx.password,
    organizationName: `POS ${TS}`, phone: '+77011115000', 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: ctx.email, password: ctx.password, organizationName: `POS ${TS}`, phone: '+77011115000', plan: 'start' })
  }
  if (r.status !== 200) throw new Error(`signup: ${r.status} ${JSON.stringify(r.data)}`)
  const sess = await login(ctx.email, ctx.password)
  ctx.token = sess.accessToken
  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'),
  ])
  ctx.storeId = stores.data.items.find((s: { isMain: boolean }) => s.isMain).id
  const unitKg = units.data.items.find((u: { code: string }) => u.code === '166').id
  const group = groups.data.items.find((g: { parentId: string | null }) => g.parentId == null).id
  const retail = prices.data.items.find((p: { isRequired: boolean }) => p.isRequired).id
  const kzt = currencies.data.items.find((c: { code: string }) => c.code === 'KZT').id
  const prod = await auth.post('/api/catalog/products', {
    name: `POS Prod ${TS}`, article: `POS-${TS}`,
    unitOfMeasureId: unitKg, vat: 0, vatEnabled: false, productGroupId: group,
    barcodes: [{ code: generateEan13(101), type: 1, isPrimary: true }],
    prices: [{ priceTypeId: retail, amount: 300, currencyId: kzt }],
  })
  ctx.productId = prod.data.id
  // Stock через Enter
  const enter = await auth.post('/api/inventory/enters', {
    date: new Date().toISOString(), storeId: ctx.storeId, currencyId: kzt,
    lines: [{ productId: ctx.productId, quantity: 100, unitCost: 200 }],
  })
  await auth.post(`/api/inventory/enters/${enter.data.id}/post`, {})
}

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

export async function pos01_setup({ ctx, step, report }: StepCtx) {
  ctx.ts = TS
  await bootstrap(ctx)
  check(step, { kind: 'api', description: 'org + product + stock=100 готовы', ok: !!ctx.token && !!ctx.storeId && !!ctx.productId, detail: `org product=${ctx.productId?.slice(0,8)}` })
}

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

export async function pos02_sync_full({ ctx, step, report }: StepCtx) {
  if (!ctx.token || !ctx.storeId) { step.status = 'skip'; return }
  const api = makeClient(ctx.token)
  const r = await api.get(`/api/pos/v1/sync?storeId=${ctx.storeId}`)
  check(step, { kind: 'api', description: 'GET /pos/v1/sync → 200', ok: r.status === 200, detail: `${r.status}` })
  if (r.status !== 200) return
  ctx.serverTime = r.data.serverTime
  check(step, { kind: 'api', description: 'products содержит наш товар', ok: r.data.products?.some((p: { id: string }) => p.id === ctx.productId), detail: `products=${r.data.products?.length}` })
  check(step, { kind: 'api', description: 'prices содержит цену продукта', ok: r.data.prices?.some((p: { productId: string }) => p.productId === ctx.productId), detail: `prices=${r.data.prices?.length}` })
  const stockRow = r.data.stocks?.find((s: { productId: string }) => s.productId === ctx.productId)
  check(step, { kind: 'api', description: 'stocks.qty = 100', ok: stockRow?.quantity === 100, detail: `qty=${stockRow?.quantity}` })
  check(step, { kind: 'api', description: 'serverTime ISO 8601', ok: !!r.data.serverTime && !isNaN(new Date(r.data.serverTime).getTime()), detail: `t=${r.data.serverTime}` })
}

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

export async function pos03_sync_incremental({ ctx, step, report }: StepCtx) {
  if (!ctx.token || !ctx.storeId || !ctx.serverTime) { step.status = 'skip'; return }
  const api = makeClient(ctx.token)
  // since=ServerTime — с этого момента ничего не изменилось → products/prices пустые
  const r = await api.get(`/api/pos/v1/sync?storeId=${ctx.storeId}&since=${encodeURIComponent(ctx.serverTime)}`)
  check(step, { kind: 'api', description: 'GET /sync?since=… → 200', ok: r.status === 200, detail: `${r.status}` })
  // Stocks всегда полный снимок (по design), но products/prices должны быть пустые
  check(step, { kind: 'api', description: 'products пустой (с last sync нет изменений)', ok: Array.isArray(r.data.products) && r.data.products.length === 0, detail: `products=${r.data.products?.length}` })
  check(step, { kind: 'api', description: 'stocks все ещё актуальный снимок', ok: Array.isArray(r.data.stocks) && r.data.stocks.length >= 1, detail: `stocks=${r.data.stocks?.length}` })
}

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

export async function pos04_sales_create({ ctx, step, report }: StepCtx) {
  if (!ctx.token || !ctx.storeId || !ctx.productId) { step.status = 'skip'; return }
  const api = makeClient(ctx.token)
  ctx.idempotencyKey1 = randomUUID()
  ctx.clientSaleId1 = randomUUID()
  const body = {
    idempotencyKey: ctx.idempotencyKey1,
    sales: [{
      clientSaleId: ctx.clientSaleId1,
      occurredAt: new Date().toISOString(),
      customerId: null, cashierUserId: null,
      payment: 0, paidCash: 600, paidCard: 0,
      lines: [{ productId: ctx.productId, quantity: 2, unitPrice: 300, discount: 0, vatPercent: 0 }],
      notes: 'pos sale',
    }],
  }
  const r = await api.post(`/api/pos/v1/sales?storeId=${ctx.storeId}`, body)
  check(step, { kind: 'api', description: 'POST /sales → 200', ok: r.status === 200, detail: `${r.status} ${asString(r.data).slice(0, 200)}` })
  if (r.status !== 200) return
  check(step, { kind: 'api', description: 'accepted=1, failed=0', ok: r.data?.accepted?.length === 1 && r.data?.failed?.length === 0, detail: `acc=${r.data?.accepted?.length} fail=${r.data?.failed?.length}` })
  check(step, { kind: 'api', description: 'replayedFromCache = false (новая продажа)', ok: r.data?.replayedFromCache === false, detail: `replayed=${r.data?.replayedFromCache}` })
  ctx.serverSaleId1 = r.data?.accepted?.[0]?.serverSaleId

  // Проверка стока: должна стать 98
  const sync = await api.get(`/api/pos/v1/sync?storeId=${ctx.storeId}`)
  const stockRow = sync.data.stocks?.find((s: { productId: string }) => s.productId === ctx.productId)
  check(step, { kind: 'api', description: 'Stock = 98 (100 - 2)', ok: stockRow?.quantity === 98, detail: `qty=${stockRow?.quantity}` })
}

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

export async function pos05_sales_replay_same_key({ ctx, step, report }: StepCtx) {
  if (!ctx.token || !ctx.storeId || !ctx.productId || !ctx.idempotencyKey1) { step.status = 'skip'; return }
  const api = makeClient(ctx.token)
  const body = {
    idempotencyKey: ctx.idempotencyKey1,
    sales: [{
      clientSaleId: ctx.clientSaleId1,
      occurredAt: new Date().toISOString(),
      customerId: null, cashierUserId: null,
      payment: 0, paidCash: 600, paidCard: 0,
      lines: [{ productId: ctx.productId, quantity: 2, unitPrice: 300, discount: 0, vatPercent: 0 }],
      notes: 'repeat',
    }],
  }
  const r = await api.post(`/api/pos/v1/sales?storeId=${ctx.storeId}`, body)
  check(step, { kind: 'api', description: 'POST /sales с тем же ключом → 200', ok: r.status === 200, detail: `${r.status}` })
  check(step, { kind: 'api', description: 'replayedFromCache = true', ok: r.data?.replayedFromCache === true, detail: `replayed=${r.data?.replayedFromCache}` })

  // Stock не упал на 4 — остался 98
  const sync = await api.get(`/api/pos/v1/sync?storeId=${ctx.storeId}`)
  const stockRow = sync.data.stocks?.find((s: { productId: string }) => s.productId === ctx.productId)
  check(step, { kind: 'api', description: 'Stock остался 98 (не дублировано списание)', ok: stockRow?.quantity === 98, detail: `qty=${stockRow?.quantity}` })
}

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

export async function pos06_sales_new_key_same_client_sale_id({ ctx, step, report }: StepCtx) {
  if (!ctx.token || !ctx.storeId || !ctx.productId || !ctx.clientSaleId1 || !ctx.serverSaleId1) { step.status = 'skip'; return }
  const api = makeClient(ctx.token)
  // Новый idempotencyKey, но тот же ClientSaleId — per-sale идемпотентность
  // должна найти существующий RetailSale по маркеру в Notes.
  const body = {
    idempotencyKey: randomUUID(),
    sales: [{
      clientSaleId: ctx.clientSaleId1,
      occurredAt: new Date().toISOString(),
      customerId: null, cashierUserId: null,
      payment: 0, paidCash: 600, paidCard: 0,
      lines: [{ productId: ctx.productId, quantity: 2, unitPrice: 300, discount: 0, vatPercent: 0 }],
      notes: 'same client sale id',
    }],
  }
  const r = await api.post(`/api/pos/v1/sales?storeId=${ctx.storeId}`, body)
  check(step, { kind: 'api', description: 'POST → 200', ok: r.status === 200, detail: `${r.status}` })
  const acc = r.data?.accepted?.[0]
  check(step, {
    kind: 'api', description: 'accepted с тем же ServerSaleId (per-sale дедуп)',
    ok: acc?.serverSaleId === ctx.serverSaleId1,
    detail: `serverSaleId=${acc?.serverSaleId} vs first=${ctx.serverSaleId1}`,
  })
  // Stock остался 98
  const sync = await api.get(`/api/pos/v1/sync?storeId=${ctx.storeId}`)
  const stockRow = sync.data.stocks?.find((s: { productId: string }) => s.productId === ctx.productId)
  check(step, { kind: 'api', description: 'Stock остался 98 (нет дубля)', ok: stockRow?.quantity === 98, detail: `qty=${stockRow?.quantity}` })
}

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

export async function pos07_sales_insufficient_stock({ ctx, step, report }: StepCtx) {
  if (!ctx.token || !ctx.storeId || !ctx.productId) { step.status = 'skip'; return }
  const api = makeClient(ctx.token)
  const body = {
    idempotencyKey: randomUUID(),
    sales: [{
      clientSaleId: randomUUID(),
      occurredAt: new Date().toISOString(),
      customerId: null, cashierUserId: null,
      payment: 0, paidCash: 0, paidCard: 0,
      lines: [{ productId: ctx.productId, quantity: 10000, unitPrice: 300, discount: 0, vatPercent: 0 }],
      notes: 'over stock',
    }],
  }
  const r = await api.post(`/api/pos/v1/sales?storeId=${ctx.storeId}`, body)
  check(step, { kind: 'api', description: 'POST с qty > stock → 200 (failed-секция)', ok: r.status === 200, detail: `${r.status}` })
  check(step, {
    kind: 'api', description: 'failed.length = 1 с error о недостатке остатка',
    ok: r.data?.failed?.length === 1 && /остат|недостат|stock|нал/i.test(r.data?.failed?.[0]?.error ?? ''),
    detail: `failed=${asString(r.data?.failed?.[0]).slice(0, 200)}`,
  })
  check(step, { kind: 'api', description: 'accepted = 0', ok: r.data?.accepted?.length === 0, detail: `acc=${r.data?.accepted?.length}` })
}
