/**
 * Хелперы для UI-deep тестов: signup через API (быстрее чем форма), затем
 * подкладываем access_token в localStorage веба и грузим страницу.
 *
 * Также — common-listeners на console-error и failed responses: тесты
 * провалятся, если страница пишет ошибки в DevTools или возвращает 5xx.
 */
import { expect, type Page, type BrowserContext, type APIRequestContext, type ConsoleMessage } from '@playwright/test'
import { request as apiRequest } from '@playwright/test'

const BASE = process.env.E2E_ADMIN_URL ?? 'https://test.admin.food-market.kz'

export interface Session {
  email: string
  password: string
  orgName: string
  accessToken: string
  orgId: string
}

/** Signup через API (быстрее чем форма). Возвращает токен. */
export async function apiSignup(prefix = 'ui'): Promise<Session> {
  const ts = Date.now() + Math.floor(Math.random() * 1000)
  // 60s timeout — signup на холодный stage может задерживаться при первом
  // обращении (cold cache), плюс ratelimit на signup 6/min.
  const ctx = await apiRequest.newContext({ baseURL: BASE, ignoreHTTPSErrors: true, timeout: 60_000 })
  const email = `${prefix}-${ts}@food-market.local`
  const password = 'UiTest12345!'
  const orgName = `UI-${prefix}-${ts}`
  let r = await ctx.post('/api/auth/signup', {
    data: { email, password, organizationName: orgName, phone: '+77011190001', plan: 'start' },
    failOnStatusCode: false,
  })
  // ratelimit на signup: 6/min; ждём и повторяем
  for (let i = 0; i < 5 && r.status() === 429; i++) {
    await new Promise(res => setTimeout(res, 15_000))
    r = await ctx.post('/api/auth/signup', {
      data: { email, password, organizationName: orgName, phone: '+77011190001', plan: 'start' },
      failOnStatusCode: false,
    })
  }
  expect(r.status(), `signup для ${email}`).toBe(200)
  const tok = await loginToken(ctx, email, password)
  const me = await ctx.get('/api/me', { headers: { Authorization: `Bearer ${tok}` } })
  expect(me.status()).toBe(200)
  const meBody = await me.json() as { orgId: string }
  await ctx.dispose()
  return { email, password, orgName, accessToken: tok, orgId: meBody.orgId }
}

export async function loginToken(ctx: APIRequestContext, email: string, password: string): Promise<string> {
  const body = new URLSearchParams({
    grant_type: 'password',
    username: email,
    password,
    client_id: 'food-market-web',
    scope: 'openid profile email roles api offline_access',
  })
  const r = await ctx.post('/connect/token', {
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    data: body.toString(),
    failOnStatusCode: false,
  })
  expect(r.status(), `connect/token ${email}`).toBe(200)
  const j = await r.json() as { access_token: string }
  return j.access_token
}

/** Логинит браузер: устанавливает fm.access_token в localStorage и идёт на /dashboard. */
export async function attachSession(page: Page, sess: Session, gotoPath = '/dashboard') {
  await page.goto('/login')
  await page.evaluate((tok) => localStorage.setItem('fm.access_token', tok), sess.accessToken)
  await page.goto(gotoPath, { waitUntil: 'domcontentloaded' })
}

/** Слушатель console-ошибок и сетевых 5xx/неожиданных 4xx. Возвращает накопленные
 * проблемы, чтобы тест мог отдельно assert на пустоту в конце. */
export interface CollectedErrors {
  console: string[]
  network: string[]
}
export function watchPage(page: Page, opts?: {
  /** Список URL substring — ожидаемые 4xx, не считаются ошибкой (например 404 на проверку дубля артикула). */
  expected4xxContains?: string[]
  /** Список console-substr — ожидаемые ошибки (например React strict-mode dev-warn). */
  expectedConsoleContains?: string[]
}): CollectedErrors {
  const acc: CollectedErrors = { console: [], network: [] }
  page.on('console', (msg: ConsoleMessage) => {
    if (msg.type() !== 'error') return
    const t = msg.text()
    // «Failed to load resource: the server responded with a status of XXX» —
    // авто-сообщение Chromium на каждый 4xx/5xx, дублирует network-обработчик.
    // Не считаем это самостоятельной ошибкой.
    if (/^Failed to load resource: the server responded with a status of \d+/i.test(t)) return
    // Сетевые flake'и (DNS / connection reset / network change) — внешний
    // фактор, не баг приложения.
    if (/^Failed to load resource: net::(ERR_NETWORK_CHANGED|ERR_INTERNET_DISCONNECTED|ERR_CONNECTION_RESET|ERR_NAME_NOT_RESOLVED|ERR_CONNECTION_REFUSED|ERR_TIMED_OUT|ERR_ABORTED)/i.test(t)) return
    if ((opts?.expectedConsoleContains ?? []).some(s => t.includes(s))) return
    acc.console.push(t)
  })
  page.on('response', async (resp) => {
    const status = resp.status()
    if (status < 400) return
    const url = resp.url()
    // 401 без токена на API — нормально для не-залогиненных страниц
    if (status === 401 && /\/(api|connect)\//.test(url)) return
    if ((opts?.expected4xxContains ?? []).some(s => url.includes(s))) return
    if (status >= 400) {
      acc.network.push(`${status} ${resp.request().method()} ${url}`)
    }
  })
  return acc
}

/** Удобный assert-helper для пост-проверки ошибок. */
export function expectNoErrors(acc: CollectedErrors, where: string) {
  if (acc.console.length || acc.network.length) {
    const msg = [
      `Found errors on ${where}:`,
      ...acc.console.map(c => `  CONSOLE: ${c}`),
      ...acc.network.map(n => `  NET: ${n}`),
    ].join('\n')
    throw new Error(msg)
  }
}

export const STAGE_URL = BASE
