#!/usr/bin/env node

import { execFileSync } from 'node:child_process'

function fail(message) {
  console.error(`api_release_smoke=FAIL ${message}`)
  process.exit(1)
}

function assert(condition, message) {
  if (!condition) fail(message)
}

const baseURL = (process.env.RELEASE_SMOKE_BASE_URL || '').replace(/\/+$/, '')
const webURL = (process.env.RELEASE_SMOKE_WEB_URL || '').replace(/\/+$/, '')
const composeProject = process.env.RELEASE_SMOKE_COMPOSE_PROJECT || ''
const composeFile = process.env.RELEASE_SMOKE_COMPOSE_FILE || 'docker-compose.yml'
const nonce = process.env.RELEASE_SMOKE_NONCE || Date.now().toString(36)
const databaseUser = process.env.RELEASE_SMOKE_DATABASE_USER || 'easyai'
const databaseName = process.env.RELEASE_SMOKE_DATABASE_NAME || 'easyai_ai_gateway'

if (!/^http:\/\/127\.0\.0\.1:\d+$/.test(baseURL)) fail('invalid RELEASE_SMOKE_BASE_URL')
if (!/^http:\/\/127\.0\.0\.1:\d+$/.test(webURL)) fail('invalid RELEASE_SMOKE_WEB_URL')
if (!/^[a-z0-9][a-z0-9_-]{2,62}$/.test(composeProject)) fail('invalid RELEASE_SMOKE_COMPOSE_PROJECT')
if (!/^[a-z_][a-z0-9_]{0,62}$/.test(databaseUser)) fail('invalid RELEASE_SMOKE_DATABASE_USER')
if (!/^[a-z_][a-z0-9_]{0,62}$/.test(databaseName)) fail('invalid RELEASE_SMOKE_DATABASE_NAME')

async function request(path, { method = 'GET', token = '', body } = {}) {
  const response = await fetch(`${baseURL}${path}`, {
    method,
    signal: AbortSignal.timeout(15_000),
    headers: {
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
      ...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
    },
    ...(body === undefined ? {} : { body: JSON.stringify(body) }),
  })
  const text = await response.text()
  let payload
  try {
    payload = text ? JSON.parse(text) : null
  } catch {
    fail(`${method} ${path} returned non-JSON HTTP ${response.status}`)
  }
  if (!response.ok) fail(`${method} ${path} returned HTTP ${response.status}`)
  return { response, payload }
}

async function waitForAPI() {
  let lastError = 'API did not respond'
  for (let attempt = 0; attempt < 60; attempt += 1) {
    try {
      const response = await fetch(`${baseURL}/api/v1/readyz`, {
        signal: AbortSignal.timeout(2_000),
      })
      if (response.ok) return
      lastError = `readiness returned HTTP ${response.status}`
    } catch (error) {
      lastError = error.message
    }
    await new Promise((resolve) => setTimeout(resolve, 1_000))
  }
  fail(`API readiness timed out: ${lastError}`)
}

await waitForAPI()

const health = await request('/api/v1/healthz')
assert(health.payload?.ok === true && health.payload?.service === 'easyai-ai-gateway', 'health payload mismatch')

const ready = await request('/api/v1/readyz')
assert(ready.payload?.ok === true, 'readiness payload mismatch')

const openapi = await request('/api/v1/openapi.json')
for (const path of [
  '/api/v1/healthz',
  '/api/v1/readyz',
  '/api/v1/models',
  '/api/v1/chat/completions',
  '/api/v1/responses',
  '/api/v1/images/generations',
]) {
  assert(openapi.payload?.paths?.[path], `OpenAPI is missing ${path}`)
}

const username = `release_smoke_${nonce}`.slice(0, 60)
const password = `Release-Smoke-${nonce}-9x!`
const registration = await request('/api/v1/auth/register', {
  method: 'POST',
  body: { username, email: `${username}@example.invalid`, password },
})
assert(typeof registration.payload?.accessToken === 'string', 'registration did not return an access token')

const login = await request('/api/v1/auth/login', {
  method: 'POST',
  body: { account: username, password },
})
const jwt = login.payload?.accessToken
assert(typeof jwt === 'string' && jwt.length > 20, 'login did not return an access token')

try {
  execFileSync('docker', [
    'compose', '-p', composeProject, '-f', composeFile,
    'exec', '-T', 'postgres', 'psql', '-v', 'ON_ERROR_STOP=1',
    '-U', databaseUser, '-d', databaseName, '-c',
    `UPDATE gateway_wallet_accounts SET balance = 100000 WHERE gateway_user_id = (SELECT id FROM gateway_users WHERE username = '${username.replaceAll("'", "''")}')`,
  ], { stdio: ['ignore', 'ignore', 'pipe'] })
} catch {
  fail('could not fund the ephemeral smoke wallet')
}

const apiKeyResult = await request('/api/v1/api-keys', {
  method: 'POST', token: jwt, body: { name: 'release smoke key' },
})
const apiKey = apiKeyResult.payload?.secret
assert(typeof apiKey === 'string' && apiKey.startsWith('sk-gw-'), 'API key creation failed')

const models = await request('/api/v1/models', { token: apiKey })
assert(Array.isArray(models.payload?.items) || Array.isArray(models.payload?.data), 'models response has no items')

const simulation = { runMode: 'simulation', simulation: true, simulationDurationMs: 5 }
const chat = await request('/api/v1/chat/completions', {
  method: 'POST', token: apiKey,
  body: { model: 'openai:gpt-4o-mini', messages: [{ role: 'user', content: 'release smoke' }], ...simulation },
})
assert(/^chatcmpl-/.test(chat.payload?.id || ''), 'chat simulation response mismatch')

const responses = await request('/api/v1/responses', {
  method: 'POST', token: apiKey,
  body: { model: 'openai:gpt-4o-mini', input: 'release smoke', ...simulation },
})
assert(/^resp/.test(responses.payload?.id || ''), 'responses simulation response mismatch')

const images = await request('/api/v1/images/generations', {
  method: 'POST', token: apiKey,
  body: { model: 'openai:gpt-image-1', prompt: 'release smoke', ...simulation },
})
assert(Array.isArray(images.payload?.data) && images.payload.data.length > 0, 'image simulation response mismatch')

const webHealth = await fetch(`${webURL}/api/v1/healthz`, { signal: AbortSignal.timeout(15_000) })
assert(webHealth.ok, `web API reverse proxy returned HTTP ${webHealth.status}`)
const webApp = await fetch(`${webURL}/`, { signal: AbortSignal.timeout(15_000) })
assert(webApp.ok && (await webApp.text()).includes('EasyAI AI Gateway'), 'web application smoke failed')

console.log('api_release_smoke=PASS health=ok readiness=ok openapi=ok auth=ok chat=ok responses=ok images=ok web=ok')
