import { describe, it, expect, afterEach } from 'vitest';
import { createServer as createHttpsServer, type Server } from 'https';
import { request as httpsRequest } from 'https';
import { mkdtempSync, rmSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { resolveTlsOptions } from '../net/tls-options.js';
import { SERVER_TLS_DEFAULTS } from '../server/config.js';

describe('native HTTPS listener (self-signed)', () => {
  let server: Server | undefined;
  let dir: string | undefined;
  afterEach(async () => {
    if (server) await new Promise<void>((r) => server!.close(() => r()));
    if (dir) rmSync(dir, { recursive: true, force: true });
    server = undefined;
    dir = undefined;
  });

  it('completes a TLS>=1.2 handshake and serves the app over https', { timeout: 15000 }, async () => {
    dir = mkdtempSync(join(tmpdir(), 'tls-listener-'));
    const resolved = resolveTlsOptions({ ...SERVER_TLS_DEFAULTS, enabled: true, selfSignedDir: dir });
    server = createHttpsServer(
      { cert: resolved.cert, key: resolved.key, minVersion: resolved.minVersion },
      (_req, res) => {
        res.writeHead(200);
        res.end('ok');
      },
    );
    await new Promise<void>((r) => server!.listen(0, '127.0.0.1', r));
    const addr = server.address();
    const port = typeof addr === 'object' && addr ? addr.port : 0;

    const result = await new Promise<{ code: number; body: string; proto: string | null }>(
      (resolve, reject) => {
        const req = httpsRequest(
          { host: '127.0.0.1', port, path: '/', rejectUnauthorized: false },
          (res) => {
            // Capture protocol before the socket is torn down (socket may be
            // null by the time 'end' fires so we snapshot it on 'response').
            const proto = (res.socket as import('tls').TLSSocket | null)?.getProtocol?.() ?? null;
            let d = '';
            res.on('data', (c) => (d += c));
            res.on('end', () =>
              resolve({ code: res.statusCode ?? 0, body: d, proto }),
            );
          },
        );
        req.on('error', reject);
        req.end();
      },
    );
    expect(result.code).toBe(200);
    expect(result.body).toBe('ok');
    expect(['TLSv1.2', 'TLSv1.3']).toContain(result.proto);
  });

  it('a strict client rejects the self-signed cert', async () => {
    dir = mkdtempSync(join(tmpdir(), 'tls-listener-strict-'));
    const resolved = resolveTlsOptions({ ...SERVER_TLS_DEFAULTS, enabled: true, selfSignedDir: dir });
    server = createHttpsServer(
      { cert: resolved.cert, key: resolved.key, minVersion: resolved.minVersion },
      (_req, res) => {
        res.writeHead(200);
        res.end('ok');
      },
    );
    await new Promise<void>((r) => server!.listen(0, '127.0.0.1', r));
    const addr = server.address();
    const port = typeof addr === 'object' && addr ? addr.port : 0;
    const outcome = await new Promise<string>((resolve) => {
      const req = httpsRequest(
        { host: '127.0.0.1', port, path: '/', rejectUnauthorized: true },
        () => resolve('UNEXPECTED_OK'),
      );
      req.on('error', (e) => resolve('rejected:' + (e as NodeJS.ErrnoException).code));
      req.end();
    });
    expect(outcome).toMatch(/^rejected:/);
  });
});
