/**
 * Stage smoke: signup → bootstrap → login. Все проверки против
 * https://test.admin.food-market.kz по HTTPS. БД на stage не сбрасываем
 * (она у нас изолированная), поэтому используем timestamped email/org.
 */
import { login, makeClient, ADMIN_BASE } from '../lib/api.js'
import type { CheckResult, Step, Report } from '../lib/report.js'

type Ctx = {
  apiOnly: boolean
  ts: number
  email: string
  password: string
  orgName: string
  phone: string
  organizationId?: string
  accessToken?: string
  refreshToken?: string
}

interface StepCtx { ctx: Ctx; step: Step; report: Report }

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)
}

function ensureCtx(ctx: Ctx): void {
  if (ctx.ts) return
  ctx.ts = Date.now()
  ctx.email = `stage-smoke-${ctx.ts}@food-market.local`
  ctx.password = 'Stage12345!'
  ctx.orgName = `TestStage ${ctx.ts}`
  ctx.phone = '+77011234567'
}

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

export async function stage01_signup_happy_path({ ctx, step, report }: StepCtx) {
  ensureCtx(ctx)
  const api = makeClient()
  const res = await api.post('/api/auth/signup', {
    email: ctx.email,
    password: ctx.password,
    organizationName: ctx.orgName,
    phone: ctx.phone,
    plan: 'start',
  })
  check(step, {
    kind: 'api',
    description: 'POST /api/auth/signup → 200',
    ok: res.status === 200,
    detail: res.status === 200 ? `org=${res.data?.organizationId} email=${res.data?.email}` : asString(res.data),
  })
  if (res.status !== 200) {
    report.bug({
      step: 'stage01',
      severity: 'critical',
      title: 'Публичный signup не работает на stage',
      detail: `POST /api/auth/signup → ${res.status} ${asString(res.data)} (${ADMIN_BASE})`,
    })
    return
  }
  ctx.organizationId = res.data.organizationId
  if (res.data.email !== ctx.email) {
    report.bug({
      step: 'stage01',
      severity: 'medium',
      title: 'Возвращённый email из signup не совпадает с введённым',
      detail: `sent=${ctx.email}, got=${res.data.email}`,
    })
  }
}

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

export async function stage02_bootstrap_entities_present({ ctx, step, report }: StepCtx) {
  if (!ctx.organizationId) { step.status = 'skip'; step.notes.push('Нет organization из stage01'); return }
  // Логинимся под только что созданным admin'ом — токен нужен для tenant-scoped API.
  const sess = await login(ctx.email, ctx.password)
  ctx.accessToken = sess.accessToken
  ctx.refreshToken = sess.refreshToken
  const api = makeClient(sess.accessToken)

  // Store
  const stores = await api.get('/api/catalog/stores')
  const mainStore = stores.data?.items?.find((s: { isMain: boolean }) => s.isMain)
  check(step, {
    kind: 'api',
    description: 'GET /api/catalog/stores содержит main store (isMain=true)',
    ok: stores.status === 200 && !!mainStore,
    detail: stores.status === 200 ? `total=${stores.data?.total}` : asString(stores.data),
  })

  // EmployeeRoles
  const roles = await api.get('/api/organization/employee-roles')
  const adminRole = roles.data?.items?.find((r: { name: string; isSystem: boolean }) => r.isSystem && r.name === 'Администратор')
  check(step, {
    kind: 'api',
    description: 'EmployeeRoles содержит системную «Администратор»',
    ok: roles.status === 200 && !!adminRole,
    detail: roles.status === 200 ? `roles=${roles.data?.items?.length}` : asString(roles.data),
  })
  if (!adminRole) {
    report.bug({
      step: 'stage02', severity: 'critical',
      title: 'Bootstrap не создал системную роль «Администратор»',
      detail: 'Без неё admin не сможет видеть страницы — это критично.',
    })
  }

  // UnitsOfMeasure
  const units = await api.get('/api/catalog/units-of-measure')
  const enabledUnits = (units.data?.items ?? []).filter((u: { isEnabledForOrg: boolean }) => u.isEnabledForOrg)
  check(step, {
    kind: 'api',
    description: 'UnitsOfMeasure для org содержат как минимум kg/l/m/упак (≥4 enabled)',
    ok: units.status === 200 && enabledUnits.length >= 4,
    detail: `enabled=${enabledUnits.length}`,
  })

  // ProductGroups
  const groups = await api.get('/api/catalog/product-groups')
  const rootGroup = (groups.data?.items ?? []).find((g: { name: string; parentId: string | null }) => g.name === 'Все товары' && g.parentId == null)
  check(step, {
    kind: 'api',
    description: 'ProductGroups содержит корневую «Все товары» (parentId=null)',
    ok: groups.status === 200 && !!rootGroup,
    detail: groups.status === 200 ? `total=${groups.data?.total}` : asString(groups.data),
  })

  // PriceTypes
  const prices = await api.get('/api/catalog/price-types')
  const retailPrice = (prices.data?.items ?? []).find((p: { isRetail: boolean; isRequired: boolean }) => p.isRetail && p.isRequired)
  check(step, {
    kind: 'api',
    description: 'PriceTypes содержит системную розничную (isRetail=true,isRequired=true)',
    ok: prices.status === 200 && !!retailPrice,
    detail: prices.status === 200 ? `total=${prices.data?.total}` : asString(prices.data),
  })

  // RetailPoints
  const points = await api.get('/api/catalog/retail-points')
  check(step, {
    kind: 'api',
    description: 'RetailPoints содержит хотя бы одну точку (bootstrap «Касса 1»)',
    ok: points.status === 200 && (points.data?.items?.length ?? 0) >= 1,
    detail: points.status === 200 ? `total=${points.data?.total}` : asString(points.data),
  })
}

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

export async function stage03_login_returns_access_refresh({ ctx, step, report }: StepCtx) {
  if (!ctx.email) { step.status = 'skip'; return }
  const sess = await login(ctx.email, ctx.password)
  check(step, {
    kind: 'api',
    description: 'Логин возвращает access_token',
    ok: !!sess.accessToken && sess.accessToken.length > 100,
    detail: `len=${sess.accessToken?.length}`,
  })
  check(step, {
    kind: 'api',
    description: 'Логин возвращает refresh_token (offline_access scope)',
    ok: !!sess.refreshToken && sess.refreshToken.length > 100,
    detail: `len=${sess.refreshToken?.length}`,
  })
  if (!sess.refreshToken) {
    report.bug({
      step: 'stage03', severity: 'high',
      title: 'Refresh token не выдан при логине',
      detail: 'OpenIddict должен выдавать refresh_token при scope offline_access — без него рефреш UI ломается.',
    })
  }
  check(step, {
    kind: 'api',
    description: '/api/me возвращает orgId = тому что вернул signup',
    ok: sess.orgId === ctx.organizationId,
    detail: `me.orgId=${sess.orgId}, signup.orgId=${ctx.organizationId}`,
  })
  check(step, {
    kind: 'api',
    description: '/api/me возвращает role=Admin',
    ok: sess.roles.includes('Admin'),
    detail: sess.roles.join(','),
  })
}

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

export async function stage04_signup_edge_cases({ ctx, step, report }: StepCtx) {
  ensureCtx(ctx)
  const api = makeClient()
  const ts = ctx.ts
  // Между запросами — задержка, чтобы не словить rate-limiter на /api/auth/signup
  // (он включён и срабатывает на 5+ запросах в окне).
  const wait = (ms: number) => new Promise((r) => setTimeout(r, ms))

  // 1. Дубликат email — запрос делаем с уже использованным email из stage01.
  const dup = await api.post('/api/auth/signup', {
    email: ctx.email, password: ctx.password,
    organizationName: 'Dup ' + ts, phone: ctx.phone, plan: 'start',
  })
  check(step, {
    kind: 'api',
    description: 'Дубликат email → 400 с понятным сообщением',
    ok: dup.status === 400 && /уже зарегистр|already/i.test(asString(dup.data)),
    detail: `${dup.status} ${asString(dup.data).slice(0, 200)}`,
  })

  await wait(800)

  // 2. Короткий пароль
  const short = await api.post('/api/auth/signup', {
    email: `short-${ts}@food-market.local`, password: 'Ab1!',
    organizationName: 'Short ' + ts, phone: ctx.phone, plan: 'start',
  })
  check(step, {
    kind: 'api',
    description: 'Пароль < 8 → 400 с понятным сообщением',
    ok: short.status === 400 && /мин|min|8/i.test(asString(short.data)),
    detail: `${short.status} ${asString(short.data).slice(0, 200)}`,
  })

  await wait(800)

  // 3. Пустое название
  const empty = await api.post('/api/auth/signup', {
    email: `empty-${ts}@food-market.local`, password: ctx.password,
    organizationName: '', phone: ctx.phone, plan: 'start',
  })
  check(step, {
    kind: 'api',
    description: 'Пустое orgName → 400',
    ok: empty.status === 400,
    detail: `${empty.status} ${asString(empty.data).slice(0, 200)}`,
  })

  await wait(800)

  // 4. Кривой телефон
  const badphone = await api.post('/api/auth/signup', {
    email: `bad-${ts}@food-market.local`, password: ctx.password,
    organizationName: 'BadPhone ' + ts, phone: 'abc-not-a-phone', plan: 'start',
  })
  check(step, {
    kind: 'api',
    description: 'Невалидный phone → 400 (валидация ФЛК Казахстана)',
    ok: badphone.status === 400 && /кор?рект|казах/i.test(asString(badphone.data)),
    detail: `${badphone.status} ${asString(badphone.data).slice(0, 200)}`,
  })
}

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

export async function stage05_public_app_url_check({ ctx, step, report }: StepCtx) {
  // Stage публичный сайт — отдельный поддомен с тем же кодом что прод-public
  // (по постановке задачи). Это значит ссылка «Войти» и POST signup в нём
  // указывают на ПРОД admin'a. Для stage-testing это означает, что forma
  // signup на https://test.food-market.kz/signup НЕ годится — нужно
  // регистрироваться напрямую на test.admin (как мы и делаем в stage01).
  // Отмечаем как informational gap, чтобы не было сюрприза, что
  // public-сайт «не тестит stage».
  const api = makeClient()
  const res = await api.get('https://test.food-market.kz/signup/')
  const html = String(res.data)
  const linkMatches = [...html.matchAll(/href="https?:\/\/([^"\/]+)\/login"/g)].map((m) => m[1])
  const distinctHosts = [...new Set(linkMatches)]
  const targetsProdAdmin = distinctHosts.length > 0 && distinctHosts.every((h) => h === 'admin.food-market.kz')

  check(step, {
    kind: 'ui',
    description: 'Public site signup рендерится (хотя бы 1 ссылка на admin)',
    ok: distinctHosts.length > 0,
    detail: `hosts=[${distinctHosts.join(', ')}]`,
  })
  if (targetsProdAdmin) {
    report.gap(
      'Stage-public (test.food-market.kz) использует тот же build что прод-public — ' +
      'signup-форма и ссылка «Войти» указывают на ПРОД admin (admin.food-market.kz). ' +
      'Для stage-testing нужно регистрироваться напрямую POST на test.admin.food-market.kz/api/auth/signup ' +
      '(stage-smoke это и делает), либо отдельно деплоить stage-public с PUBLIC_APP_URL=https://test.admin.food-market.kz.',
    )
  }
}
