import { describe, expect, it, vi } from 'vitest'
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import type { AgentConfig } from '../src/config/agentConfig.js'
import { generateIdentity } from '../src/keys/identity.js'
import { openKeystore } from '../src/keys/keystore.js'
import {
  CertExpiredError,
  NotEnrolledError,
  buildTlsOptions,
  dialRelay,
  type RawTlsWs,
  type TlsWsConstructor,
} from '../src/transport/dial.js'

const CFG: AgentConfig = {
  relayUrl: 'wss://relay.example.com/agent',
  enrollUrl: 'https://example.com/enroll',
  stateDir: '/tmp/x',
  localTargetUrl: 'ws://127.0.0.1:3000',
  subdomain: 'host-42',
  hostId: 'h-1',
}

function enrolledKs() {
  const dir = mkdtempSync(join(tmpdir(), 'wta-dial-'))
  const ks = openKeystore(dir)
  ks.saveIdentity(generateIdentity())
  ks.saveCert('CERTPEM', 'CAPEM')
  return { dir, ks }
}

const future: () => { validTo: Date } = () => ({ validTo: new Date(Date.now() + 86_400_000) })
const past: () => { validTo: Date } = () => ({ validTo: new Date(Date.now() - 1000) })

describe('buildTlsOptions (T12, INV14/INV4)', () => {
  it('wires cert + key + CA and forces rejectUnauthorized true', () => {
    const { dir, ks } = enrolledKs()
    const tls = buildTlsOptions(ks, { certParser: future })
    expect(tls.cert).toBe('CERTPEM')
    expect(tls.ca).toBe('CAPEM')
    expect(tls.key).toContain('PRIVATE KEY') // in-process key PEM
    expect(tls.rejectUnauthorized).toBe(true)
    rmSync(dir, { recursive: true, force: true })
  })

  it('carries NO bearer/agent token (mTLS is the auth)', () => {
    const { dir, ks } = enrolledKs()
    const tls = buildTlsOptions(ks, { certParser: future })
    expect(JSON.stringify(tls)).not.toMatch(/token|authorization|bearer/i)
    rmSync(dir, { recursive: true, force: true })
  })

  it('missing cert → NotEnrolledError (fail-fast)', () => {
    const dir = mkdtempSync(join(tmpdir(), 'wta-dial-'))
    expect(() => buildTlsOptions(openKeystore(dir))).toThrow(NotEnrolledError)
    rmSync(dir, { recursive: true, force: true })
  })

  it('expired cert → CertExpiredError', () => {
    const { dir, ks } = enrolledKs()
    expect(() => buildTlsOptions(ks, { certParser: past })).toThrow(CertExpiredError)
    rmSync(dir, { recursive: true, force: true })
  })
})

describe('dialRelay (T12)', () => {
  it('constructs the wss client with the TLS options and resolves on open', async () => {
    const { dir, ks } = enrolledKs()
    let capturedUrl = ''
    let capturedOpts: unknown
    class FakeTlsWs implements RawTlsWs {
      constructor(url: string, opts: unknown) {
        capturedUrl = url
        capturedOpts = opts
        queueMicrotask(() => this.openCb?.())
      }
      private openCb?: () => void
      send(): void {}
      close(): void {}
      on(): void {}
      once(ev: string, cb: () => void): void {
        if (ev === 'open') this.openCb = cb
      }
    }
    const ws = await dialRelay(CFG, ks, { Ctor: FakeTlsWs as unknown as TlsWsConstructor, certParser: future })
    expect(capturedUrl).toBe('wss://relay.example.com/agent')
    expect((capturedOpts as { rejectUnauthorized: boolean }).rejectUnauthorized).toBe(true)
    expect(ws).toBeDefined()
    rmSync(dir, { recursive: true, force: true })
  })

  it('rejects when the server errors before open (bad chain / MITM)', async () => {
    const { dir, ks } = enrolledKs()
    class FakeTlsWs implements RawTlsWs {
      private errCb?: (e: unknown) => void
      constructor() {
        queueMicrotask(() => this.errCb?.(new Error('unable to verify leaf signature')))
      }
      send(): void {}
      close(): void {}
      on(): void {}
      once(ev: string, cb: (e: unknown) => void): void {
        if (ev === 'error') this.errCb = cb
      }
    }
    const p = dialRelay(CFG, ks, { Ctor: FakeTlsWs as unknown as TlsWsConstructor, certParser: future })
    await expect(p).rejects.toThrow(/verify/)
    rmSync(dir, { recursive: true, force: true })
  })
})
