/**
 * Step-handlers для superadmin-console.
 *
 * Проверяем платформенные операции SuperAdmin над организациями и журнал
 * аудита. Ключевые инварианты: архив/удаление требуют точного имени для
 * подтверждения; hard-delete доступен только после retention-периода и
 * деактивирует юзеров; смена владельца требует осмысленный reason (≥10);
 * каждая мутация оставляет строку в super_admin_audit_log.
 */
import { login, makeClient } from '../lib/api.js'
import { psql } from '../lib/db.js'
import type { CheckResult, Step, Report } from '../lib/report.js'
import type { AxiosInstance } from 'axios'

const TS = Date.now()

interface Ctx {
  apiOnly: boolean
  sa?: string
  org1Id?: string
  org1Name?: string
  org1AdminEmail?: string
  org1AdminPwd?: string
  org2Id?: string
  org2Name?: string
  newOwnerUserId?: 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
  try { return JSON.stringify(x).slice(0, 200) } catch { return String(x) }
}
function q1(sql: string): string { return (psql(sql).trim().split('\n')[0] ?? '').trim() }

async function ensureSa(ctx: Ctx): Promise<string> {
  if (!ctx.sa) ctx.sa = (await login('admin@food-market.local', 'Admin12345!')).accessToken
  return ctx.sa
}
async function createOrg(api: AxiosInstance, name: string) {
  return api.post('/api/super-admin/organizations', {
    org: { name, countryCode: 'KZ', bin: null, address: null, phone: null, email: null, defaultCurrencyId: null, accountOwnerUserId: null },
    adminLastName: 'Owner', adminFirstName: 'Admin', adminEmail: `sac-${name.replace(/\s+/g, '').toLowerCase()}@example.kz`, adminPosition: null,
  })
}
// Какие ActionType есть в audit-log для организации.
async function auditActions(api: AxiosInstance, orgId: string): Promise<string[]> {
  const r = await api.get(`/api/super-admin/audit-log?organizationId=${orgId}&pageSize=100`)
  return (r.data?.items ?? []).map((x: { actionType: string }) => x.actionType)
}

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

export async function step01_create_org_audited({ ctx, step, report }: StepCtx) {
  const sa = await ensureSa(ctx)
  const api = makeClient(sa)
  ctx.org1Name = `Console Org1 ${TS}`
  const r = await createOrg(api, ctx.org1Name)
  check(step, { kind: 'api', description: 'Создание орг1 → 200', ok: r.status === 200, detail: `status=${r.status}` })
  ctx.org1Id = r.data?.organization?.id
  ctx.org1AdminEmail = r.data?.adminEmail
  ctx.org1AdminPwd = r.data?.adminTempPassword
  if (!ctx.org1Id) { report.bug({ step: '01', severity: 'critical', title: 'Орг не создана', detail: asString(r.data) }); return }
  const actions = await auditActions(api, ctx.org1Id)
  check(step, { kind: 'api', description: 'audit-log содержит CreateOrg', ok: actions.includes('CreateOrg'), detail: `actions=${actions.join(',')}` })
}

export async function step02_archive({ ctx, step }: StepCtx) {
  if (!ctx.org1Id) { step.status = 'skip'; return }
  const api = makeClient(ctx.sa!)
  const wrong = await api.post(`/api/super-admin/organizations/${ctx.org1Id}/archive`, { confirmationName: 'неверное имя' })
  check(step, { kind: 'api', description: 'Архив с неверным именем → 400', ok: wrong.status === 400, detail: `status=${wrong.status}` })
  const ok = await api.post(`/api/super-admin/organizations/${ctx.org1Id}/archive`, { confirmationName: ctx.org1Name })
  check(step, { kind: 'api', description: 'Архив с верным именем → 204', ok: ok.status === 204, detail: `status=${ok.status}` })
  const arch = q1(`SELECT "IsArchived" FROM organizations WHERE "Id"='${ctx.org1Id}'`)
  check(step, { kind: 'db', description: 'IsArchived=true, ArchivedAt задан',
    ok: (arch === 't' || arch === 'true'), detail: `IsArchived=${arch}` })
  check(step, { kind: 'api', description: 'audit-log содержит ArchiveOrg', ok: (await auditActions(api, ctx.org1Id)).includes('ArchiveOrg') })
}

export async function step03_restore({ ctx, step }: StepCtx) {
  if (!ctx.org1Id) { step.status = 'skip'; return }
  const api = makeClient(ctx.sa!)
  const r = await api.post(`/api/super-admin/organizations/${ctx.org1Id}/restore`, {})
  check(step, { kind: 'api', description: 'Восстановление → 204', ok: r.status === 204, detail: `status=${r.status}` })
  const arch = q1(`SELECT "IsArchived" FROM organizations WHERE "Id"='${ctx.org1Id}'`)
  check(step, { kind: 'db', description: 'IsArchived=false', ok: arch === 'f' || arch === 'false', detail: `IsArchived=${arch}` })
  check(step, { kind: 'api', description: 'audit-log содержит RestoreOrg', ok: (await auditActions(api, ctx.org1Id)).includes('RestoreOrg') })
}

export async function step04_change_owner({ ctx, step, report }: StepCtx) {
  if (!ctx.org1Id || !ctx.org1AdminEmail || !ctx.org1AdminPwd) { step.status = 'skip'; return }
  const sa = makeClient(ctx.sa!)
  // Заводим второго сотрудника-с-учёткой в орг1, чтобы передать владение РЕАЛЬНО
  // другому пользователю (логинимся админом орг1 — его creds сохранены в step01).
  const orgAdmin = makeClient((await login(ctx.org1AdminEmail, ctx.org1AdminPwd)).accessToken)
  const roleId = (await orgAdmin.get('/api/organization/employee-roles')).data?.items?.find((r: { name: string }) => r.name !== 'Администратор')?.id
  const emp = await orgAdmin.post('/api/organization/employees', {
    lastName: 'Преемник', firstName: 'Влад', roleId, isActive: true, createAccount: true, email: `heir-${TS}@example.kz`,
  })
  ctx.newOwnerUserId = emp.data?.employee?.userId
  check(step, { kind: 'api', description: 'Второй пользователь орг1 создан (кандидат во владельцы)', ok: !!ctx.newOwnerUserId })
  if (!ctx.newOwnerUserId) return

  const noReason = await sa.post(`/api/super-admin/organizations/${ctx.org1Id}/change-owner`, { newOwnerUserId: ctx.newOwnerUserId })
  check(step, { kind: 'api', description: 'change-owner без reason → 400', ok: noReason.status === 400, detail: `status=${noReason.status}` })
  const shortReason = await sa.post(`/api/super-admin/organizations/${ctx.org1Id}/change-owner`, { newOwnerUserId: ctx.newOwnerUserId, reason: 'ok' })
  check(step, { kind: 'api', description: 'change-owner reason<10 → 400', ok: shortReason.status === 400, detail: `status=${shortReason.status}` })
  if (shortReason.status !== 400) report.bug({ step: '04', severity: 'medium',
    title: 'change-owner принимает слишком короткий reason', detail: `reason="ok" → ${shortReason.status}, ожидалось 400 (ТЗ 2.8)` })
  const okReason = await sa.post(`/api/super-admin/organizations/${ctx.org1Id}/change-owner`,
    { newOwnerUserId: ctx.newOwnerUserId, reason: 'Передача владения по тикету поддержки #4242' })
  check(step, { kind: 'api', description: 'change-owner валидный reason → 204', ok: okReason.status === 204, detail: `status=${okReason.status}` })
  const dbOwner = q1(`SELECT "AccountOwnerUserId" FROM organizations WHERE "Id"='${ctx.org1Id}'`)
  check(step, { kind: 'db', description: 'AccountOwnerUserId обновлён на нового владельца', ok: dbOwner === ctx.newOwnerUserId, detail: `owner=${dbOwner}` })
  const actions = await auditActions(sa, ctx.org1Id)
  check(step, { kind: 'api', description: 'audit-log содержит ChangeOwner', ok: actions.includes('ChangeOwner'), detail: `actions=${actions.join(',')}` })
}

export async function step05_hard_delete({ ctx, step, report }: StepCtx) {
  const sa = makeClient(ctx.sa!)
  ctx.org2Name = `Console Org2 ${TS}`
  const created = await createOrg(sa, ctx.org2Name)
  ctx.org2Id = created.data?.organization?.id
  check(step, { kind: 'api', description: 'Орг2 создана', ok: !!ctx.org2Id })
  if (!ctx.org2Id) return

  // Удаление неархивной → 409.
  const delLive = await sa.delete(`/api/super-admin/organizations/${ctx.org2Id}`, { data: { confirmationName: ctx.org2Name } })
  check(step, { kind: 'api', description: 'Удаление неархивной орг → 409', ok: delLive.status === 409, detail: `status=${delLive.status}` })

  await sa.post(`/api/super-admin/organizations/${ctx.org2Id}/archive`, { confirmationName: ctx.org2Name })
  // До истечения retention (дефолт 30 дней, ArchivedAt=сейчас) → 409.
  const delEarly = await sa.delete(`/api/super-admin/organizations/${ctx.org2Id}`, { data: { confirmationName: ctx.org2Name } })
  check(step, { kind: 'api', description: 'Удаление до retention → 409', ok: delEarly.status === 409, detail: `status=${delEarly.status} ${asString(delEarly.data)}` })

  // retention=0 → удаление сразу доступно.
  await sa.put('/api/super-admin/settings', { archiveRetentionDays: 0 })
  const delWrong = await sa.delete(`/api/super-admin/organizations/${ctx.org2Id}`, { data: { confirmationName: 'не то имя' } })
  check(step, { kind: 'api', description: 'Удаление с неверным именем → 400', ok: delWrong.status === 400, detail: `status=${delWrong.status}` })

  // userId'ы орг2 до удаления — проверим, что после удаления они деактивированы.
  const usersBefore = q1(`SELECT count(*) FROM users WHERE "OrganizationId"='${ctx.org2Id}'`)
  const delOk = await sa.delete(`/api/super-admin/organizations/${ctx.org2Id}`, { data: { confirmationName: ctx.org2Name } })
  check(step, { kind: 'api', description: 'Удаление архивной (retention=0, верное имя) → 204', ok: delOk.status === 204, detail: `status=${delOk.status}` })
  const orgGone = q1(`SELECT count(*) FROM organizations WHERE "Id"='${ctx.org2Id}'`)
  check(step, { kind: 'db', description: 'Организация физически удалена', ok: orgGone === '0', detail: `rows=${orgGone}` })
  const stillLinked = q1(`SELECT count(*) FROM users WHERE "OrganizationId"='${ctx.org2Id}'`)
  check(step, { kind: 'db', description: 'Юзеры отвязаны/деактивированы (нет привязки к удалённой орг)',
    ok: stillLinked === '0', detail: `before=${usersBefore} stillLinked=${stillLinked}` })
  if (orgGone !== '0') report.bug({ step: '05', severity: 'high', title: 'Hard-delete не удалил организацию', detail: `rows=${orgGone}` })

  // Возвращаем retention в дефолт, чтобы не влиять на другие прогоны.
  await sa.put('/api/super-admin/settings', { archiveRetentionDays: 30 })
}

export async function step06_audit_log_filters({ ctx, step, report }: StepCtx) {
  const sa = makeClient(ctx.sa!)
  if (ctx.org1Id) {
    const a1 = await auditActions(sa, ctx.org1Id)
    const need = ['CreateOrg', 'ArchiveOrg', 'RestoreOrg', 'ChangeOwner']
    const missing = need.filter((n) => !a1.includes(n))
    check(step, { kind: 'api', description: `audit орг1 содержит ${need.join('/')}`, ok: missing.length === 0, detail: missing.length ? `missing=${missing.join(',')}` : `actions=${a1.join(',')}` })
  }
  if (ctx.org2Id) {
    // Журнал не привязан FK к орг — запись DeleteOrg переживает удаление.
    const r = await sa.get(`/api/super-admin/audit-log?organizationId=${ctx.org2Id}&actionType=DeleteOrg`)
    const items = r.data?.items ?? []
    check(step, { kind: 'api', description: 'Фильтр actionType=DeleteOrg для орг2 возвращает запись', ok: items.length >= 1, detail: `count=${items.length}` })
    check(step, { kind: 'api', description: 'Фильтр actionType отсекает прочее (все строки DeleteOrg)',
      ok: items.every((x: { actionType: string }) => x.actionType === 'DeleteOrg'), detail: `types=${items.map((x: any) => x.actionType).join(',')}` })
  }
  // ChangeOwner хранит reason.
  const ch = await sa.get('/api/super-admin/audit-log?actionType=ChangeOwner&pageSize=20')
  const rows = ch.data?.items ?? []
  const withReason = rows.find((x: { reason?: string }) => !!x.reason)
  check(step, { kind: 'api', description: 'ChangeOwner в журнале хранит reason', ok: !!withReason, detail: `reason=${withReason?.reason ?? '(нет)'}` })
}
