/**
 * @vitest-environment node
 *
 * Integration test: built server on 3000 (API) and 8089 (CoT). Uses temp DB and bootstrap
 * user so CoT auth can be asserted (socket stays open on success).
 */
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { spawn, execSync } from 'node:child_process'
import { connect } from 'node:tls'
import { createServer } from 'node:net'
import { existsSync, mkdirSync } from 'node:fs'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { tmpdir } from 'node:os'
import { buildAuthCotXml } from '../helpers/fakeAtakClient.js'

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'

const __dirname = dirname(fileURLToPath(import.meta.url))
const projectRoot = join(__dirname, '../..')
const devCertsDir = join(projectRoot, '.dev-certs')
const devKey = join(devCertsDir, 'key.pem')
const devCert = join(devCertsDir, 'cert.pem')

const COT_AUTH_USER = 'test'
const COT_AUTH_PASS = 'test'

function getFreePort() {
  return new Promise((resolve, reject) => {
    const server = createServer()
    server.listen(0, () => {
      const address = server.address()
      const port = typeof address === 'object' && address ? address.port : 0
      server.close(() => resolve(port))
    })
    server.on('error', reject)
  })
}

function ensureDevCerts() {
  if (existsSync(devKey) && existsSync(devCert)) return
  mkdirSync(devCertsDir, { recursive: true })
  execSync(
    `openssl req -x509 -newkey rsa:2048 -keyout "${devKey}" -out "${devCert}" -days 365 -nodes -subj "/CN=localhost" -addext "subjectAltName=IP:127.0.0.1,DNS:localhost"`,
    { cwd: projectRoot, stdio: 'pipe' },
  )
}

const FETCH_TIMEOUT_MS = 5000

async function waitForHealth(apiPort, timeoutMs = 90000) {
  const start = Date.now()
  while (Date.now() - start < timeoutMs) {
    for (const protocol of ['https', 'http']) {
      try {
        const baseURL = `${protocol}://localhost:${apiPort}`
        const ctrl = new AbortController()
        const t = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS)
        const res = await fetch(`${baseURL}/health`, { method: 'GET', signal: ctrl.signal })
        clearTimeout(t)
        if (res.ok) return baseURL
      }
      catch {
        // try next
      }
    }
    await new Promise(r => setTimeout(r, 1000))
  }
  throw new Error(`Health not OK on ${apiPort} within ${timeoutMs}ms`)
}

describe('Server and CoT integration', () => {
  const testState = {
    serverProcess: null,
    apiPort: 0,
    cotPort: 0,
  }

  beforeAll(async () => {
    ensureDevCerts()
    testState.apiPort = await getFreePort()
    testState.cotPort = await getFreePort()
    const serverPath = join(projectRoot, '.output', 'server', 'index.mjs')
    if (!existsSync(serverPath)) {
      execSync('npm run build', { cwd: projectRoot, stdio: 'pipe' })
    }
    const dbPath = join(tmpdir(), `kestrelos-it-${process.pid}-${Date.now()}.db`)
    const env = {
      ...process.env,
      PORT: String(testState.apiPort),
      COT_PORT: String(testState.cotPort),
      DB_PATH: dbPath,
      BOOTSTRAP_EMAIL: COT_AUTH_USER,
      BOOTSTRAP_PASSWORD: COT_AUTH_PASS,
    }
    testState.serverProcess = spawn('node', ['.output/server/index.mjs'], {
      cwd: projectRoot,
      env,
      stdio: ['ignore', 'pipe', 'pipe'],
    })
    testState.serverProcess.stdout?.on('data', d => process.stdout.write(d))
    testState.serverProcess.stderr?.on('data', d => process.stderr.write(d))
    await waitForHealth(testState.apiPort, 90000)
  }, 120000)

  afterAll(() => {
    if (testState.serverProcess?.pid) {
      testState.serverProcess.kill('SIGTERM')
    }
  })

  it('serves health on the configured API port', async () => {
    const tryProtocols = async (protocols) => {
      if (protocols.length === 0) throw new Error('No protocol succeeded')
      try {
        const res = await fetch(`${protocols[0]}://localhost:${testState.apiPort}/health`, { method: 'GET', headers: { Accept: 'application/json' } })
        if (res?.ok) return res
        return tryProtocols(protocols.slice(1))
      }
      catch {
        return tryProtocols(protocols.slice(1))
      }
    }
    const res = await tryProtocols(['https', 'http'])
    expect(res?.ok).toBe(true)
    const body = await res.json()
    expect(body).toHaveProperty('status', 'ok')
    expect(body).toHaveProperty('endpoints')
    expect(body.endpoints).toHaveProperty('ready', '/health/ready')
  })

  it('CoT: TAK client auth with username/password succeeds (socket stays open)', async () => {
    const payload = buildAuthCotXml({ username: COT_AUTH_USER, password: COT_AUTH_PASS })
    const socket = await new Promise((resolve, reject) => {
      const s = connect(testState.cotPort, '127.0.0.1', { rejectUnauthorized: false }, () => {
        s.write(payload, () => resolve(s))
      })
      s.on('error', reject)
      s.setTimeout(8000, () => reject(new Error('connect timeout')))
    })
    await new Promise(r => setTimeout(r, 600))
    expect(socket.destroyed).toBe(false)
    socket.destroy()
  })
})
