/**
 * Stage Transfer: 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
  fromStoreId: string; toStoreId: string
  productId: string
  currencyId: string; unitKgId: string; groupId: string; priceTypeRetailId: string
}
type Ctx = {
  apiOnly: boolean
  ts: number
  orgA?: Org
  orgB?: Org
  transferIdA?: 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, bcIdx: number, twoStores: boolean): Promise<Org> {
  const api = makeClient()
  const email = `stage-tr-${suffix}-${TS}@food-market.local`
  const password = 'StageTr12345!'
  const orgName = `Tr Org ${suffix} ${TS}`
  const r = await api.post('/api/auth/signup', { email, password, organizationName: orgName, 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 mainStoreId = stores.data.items.find((s: { isMain: boolean }) => s.isMain).id
  let secondStoreId = mainStoreId
  if (twoStores) {
    const second = await auth.post('/api/catalog/stores', {
      name: `Склад 2 ${suffix}`, code: `STORE2-${suffix}`,
      address: 'Алматы 2', phone: null, managerName: null, isMain: false, isActive: true,
    })
    if (second.status !== 201) throw new Error(`store2 ${suffix}: ${second.status} ${JSON.stringify(second.data)}`)
    secondStoreId = second.data.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 prod = await auth.post('/api/catalog/products', {
    name: `Tr Prod ${suffix} ${TS}`, article: `T-${suffix}-${TS}`,
    unitOfMeasureId: unitKgId, vat: 0, vatEnabled: false, productGroupId: groupId,
    barcodes: [{ code: generateEan13(bcIdx), type: 1, isPrimary: true }],
    prices: [{ priceTypeId: priceTypeRetailId, amount: 100, currencyId }],
  })
  if (prod.status !== 201) throw new Error(`prod ${suffix}: ${prod.status} ${JSON.stringify(prod.data)}`)
  return {
    orgId: r.data.organizationId, email, password, token: sess.accessToken,
    fromStoreId: mainStoreId, toStoreId: secondStoreId,
    productId: prod.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 tr01_setup({ ctx, step, report }: StepCtx) {
  ctx.ts = TS
  ctx.orgA = await bootstrapOrg('a', '+77011130001', 31, true)
  await new Promise(r => setTimeout(r, 900))
  ctx.orgB = await bootstrapOrg('b', '+77011130002', 32, false)
  // Зальём 100 на orgA.fromStore
  const a = ctx.orgA
  const api = makeClient(a.token)
  const enter = await api.post('/api/inventory/enters', {
    date: new Date().toISOString(), storeId: a.fromStoreId, currencyId: a.currencyId,
    lines: [{ productId: a.productId, quantity: 100, unitCost: 30 }],
  })
  const post = await api.post(`/api/inventory/enters/${enter.data.id}/post`, {})
  check(step, { kind: 'api', description: 'Initial Enter on FromStore=100', ok: post.status === 204, detail: `${post.status}` })
  check(step, { kind: 'api', description: 'Org A: 2 разных склада', ok: a.fromStoreId !== a.toStoreId, detail: `from=${a.fromStoreId.slice(0,8)} to=${a.toStoreId.slice(0,8)}` })
}

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

export async function tr02_create_draft({ ctx, step, report }: StepCtx) {
  if (!ctx.orgA) { step.status = 'skip'; return }
  const a = ctx.orgA
  const api = makeClient(a.token)

  // From == To → 400
  const same = await api.post('/api/inventory/transfers', {
    date: new Date().toISOString(), fromStoreId: a.fromStoreId, toStoreId: a.fromStoreId,
    notes: 'same', lines: [{ productId: a.productId, quantity: 1, unitCost: 1 }],
  })
  check(step, {
    kind: 'api', description: 'From == To → 400',
    ok: same.status === 400 && /различ|differ|same|совпад/i.test(asString(same.data)),
    detail: `${same.status} ${asString(same.data).slice(0, 200)}`,
  })

  const res = await api.post('/api/inventory/transfers', {
    date: new Date().toISOString(), fromStoreId: a.fromStoreId, toStoreId: a.toStoreId,
    notes: 'test transfer',
    lines: [{ productId: a.productId, quantity: 10, unitCost: 30 }],
  })
  check(step, { kind: 'api', description: 'POST transfers → 201', ok: res.status === 201, detail: `${res.status} ${asString(res.data).slice(0, 200)}` })
  if (res.status !== 201) return
  ctx.transferIdA = res.data.id
  check(step, { kind: 'api', description: 'Total = 10*30 = 300', ok: res.data.total === 300, detail: `total=${res.data.total}` })
  check(step, { kind: 'api', description: 'Status = Draft (0)', ok: res.data.status === 0, detail: `status=${res.data.status}` })
}

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

export async function tr03_update_draft({ ctx, step, report }: StepCtx) {
  if (!ctx.orgA || !ctx.transferIdA) { step.status = 'skip'; return }
  const a = ctx.orgA
  const api = makeClient(a.token)
  const upd = await api.put(`/api/inventory/transfers/${ctx.transferIdA}`, {
    date: new Date().toISOString(), fromStoreId: a.fromStoreId, toStoreId: a.toStoreId,
    notes: 'updated', lines: [{ productId: a.productId, quantity: 20, unitCost: 30 }],
  })
  check(step, { kind: 'api', description: 'PUT transfer → 204 (фикс EF8)', ok: upd.status === 204, detail: `${upd.status} ${asString(upd.data).slice(0, 200)}` })
  const got = await api.get(`/api/inventory/transfers/${ctx.transferIdA}`)
  check(step, {
    kind: 'api', description: 'После PUT: total=600',
    ok: got.data?.total === 600,
    detail: `total=${got.data?.total}`,
  })
}

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

export async function tr04_post_atomic({ ctx, step, report }: StepCtx) {
  if (!ctx.orgA || !ctx.transferIdA) { step.status = 'skip'; return }
  const a = ctx.orgA
  const api = makeClient(a.token)
  const fromBefore = await getStock(a.token, a.fromStoreId, a.productId)
  const toBefore = await getStock(a.token, a.toStoreId, a.productId)

  const post = await api.post(`/api/inventory/transfers/${ctx.transferIdA}/post`, {})
  check(step, { kind: 'api', description: 'POST /post → 204', ok: post.status === 204, detail: `${post.status} ${asString(post.data).slice(0, 200)}` })

  const fromAfter = await getStock(a.token, a.fromStoreId, a.productId)
  const toAfter = await getStock(a.token, a.toStoreId, a.productId)
  check(step, { kind: 'api', description: 'FromStore -= 20', ok: fromBefore - fromAfter === 20, detail: `from ${fromBefore} → ${fromAfter}` })
  check(step, { kind: 'api', description: 'ToStore += 20', ok: toAfter - toBefore === 20, detail: `to ${toBefore} → ${toAfter}` })

  // Movements: 2 на этот документ (Out + In)
  const movs = await api.get(`/api/inventory/movements?take=200`)
  const ours = (movs.data?.items ?? []).filter((m: { documentId: string }) => m.documentId === ctx.transferIdA)
  const hasOut = ours.some((m: { type: string }) => m.type === 'TransferOut')
  const hasIn = ours.some((m: { type: string }) => m.type === 'TransferIn')
  check(step, {
    kind: 'api', description: 'Создано минимум 2 StockMovement: TransferOut + TransferIn',
    ok: ours.length >= 2 && hasOut && hasIn,
    detail: `count=${ours.length} hasOut=${hasOut} hasIn=${hasIn} types=${ours.map((m: { type: string }) => m.type).join(',')}`,
  })
}

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

export async function tr05_post_more_than_stock({ ctx, step, report }: StepCtx) {
  if (!ctx.orgA) { step.status = 'skip'; return }
  const a = ctx.orgA
  const api = makeClient(a.token)
  const avail = await getStock(a.token, a.fromStoreId, a.productId)
  const big = await api.post('/api/inventory/transfers', {
    date: new Date().toISOString(), fromStoreId: a.fromStoreId, toStoreId: a.toStoreId,
    notes: 'too much', lines: [{ productId: a.productId, quantity: avail + 100, unitCost: 30 }],
  })
  if (big.status !== 201) {
    step.notes.push(`create unexpected: ${big.status} ${asString(big.data).slice(0, 200)}`)
    return
  }
  const post = await api.post(`/api/inventory/transfers/${big.data.id}/post`, {})
  check(step, {
    kind: 'api', description: 'Post с qty > available → 409',
    ok: post.status === 409 && /недостаточ|остат|stock/i.test(asString(post.data)),
    detail: `${post.status} ${asString(post.data).slice(0, 200)}`,
  })
  // Никаких StockMovement не должно появиться
  const movs = await api.get(`/api/inventory/movements?take=200`)
  const orphan = (movs.data?.items ?? []).some((m: { documentId: string }) => m.documentId === big.data.id)
  check(step, {
    kind: 'api', description: 'Документ failed post НЕ создал orphan-движений',
    ok: !orphan,
    detail: `orphan=${orphan}`,
  })
  if (orphan) {
    report.bug({
      step: 'tr05', severity: 'high',
      title: 'Failed transfer post оставил orphan StockMovement',
      detail: `transferId=${big.data.id} — нарушена атомарность транзакции`,
    })
  }
}

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

export async function tr06_unpost_no_orphan({ ctx, step, report }: StepCtx) {
  if (!ctx.orgA || !ctx.transferIdA) { step.status = 'skip'; return }
  const a = ctx.orgA
  const api = makeClient(a.token)
  const fromBefore = await getStock(a.token, a.fromStoreId, a.productId)
  const toBefore = await getStock(a.token, a.toStoreId, a.productId)

  const unpost = await api.post(`/api/inventory/transfers/${ctx.transferIdA}/unpost`, {})
  check(step, { kind: 'api', description: 'POST /unpost → 204', ok: unpost.status === 204, detail: `${unpost.status} ${asString(unpost.data).slice(0, 200)}` })

  const fromAfter = await getStock(a.token, a.fromStoreId, a.productId)
  const toAfter = await getStock(a.token, a.toStoreId, a.productId)
  check(step, { kind: 'api', description: 'FromStore += 20 (вернулся)', ok: fromAfter - fromBefore === 20, detail: `from ${fromBefore} → ${fromAfter}` })
  check(step, { kind: 'api', description: 'ToStore -= 20 (вернулся)', ok: toBefore - toAfter === 20, detail: `to ${toBefore} → ${toAfter}` })

  // Проверка: для документа должно быть 4 движения (Out+In при пост, и 2 reversal при unpost), и Sum по qty = 0.
  const movs = await api.get(`/api/inventory/movements?take=200`)
  const ours = (movs.data?.items ?? []).filter((m: { documentId: string }) => m.documentId === ctx.transferIdA)
  const sumQty = ours.reduce((s: number, m: { quantity: number; storeId: string }) => s + m.quantity, 0)
  check(step, {
    kind: 'api', description: 'Сумма quantity по всем движениям документа = 0 (нет orphan)',
    ok: Math.abs(sumQty) < 0.001,
    detail: `count=${ours.length} sumQty=${sumQty}`,
  })

  const got = await api.get(`/api/inventory/transfers/${ctx.transferIdA}`)
  check(step, {
    kind: 'api', description: 'После Unpost: Status=Draft, PostedAt=null',
    ok: got.data?.status === 0 && got.data?.postedAt == null,
    detail: `status=${got.data?.status} postedAt=${got.data?.postedAt}`,
  })
}

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

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