import { describe, it, expect } from 'vitest';
import { mergeServerConfig, SERVER_TLS_DEFAULTS, resolveListenPort, DEFAULT_SERVER_PORT } from './config.js';

describe('mergeServerConfig', () => {
  it('upgrade-safe: an absent server block disables TLS', () => {
    const cfg = mergeServerConfig(undefined, { freshInstall: false });
    expect(cfg.tls.enabled).toBe(false);
  });

  it('fresh install with absent block enables TLS', () => {
    const cfg = mergeServerConfig(undefined, { freshInstall: true });
    expect(cfg.tls.enabled).toBe(true);
  });

  it('an explicit enabled value always wins over the freshInstall default', () => {
    expect(mergeServerConfig({ tls: { enabled: false } }, { freshInstall: true }).tls.enabled).toBe(false);
    expect(mergeServerConfig({ tls: { enabled: true } }, { freshInstall: false }).tls.enabled).toBe(true);
  });

  it('fills defaults for unspecified tls fields', () => {
    const cfg = mergeServerConfig({ tls: { enabled: true } }, { freshInstall: false });
    expect(cfg.tls.minVersion).toBe(SERVER_TLS_DEFAULTS.minVersion);
    expect(cfg.tls.selfSignedDir).toBe(SERVER_TLS_DEFAULTS.selfSignedDir);
    expect(cfg.tls.httpRedirect).toBe(true);
    expect(cfg.tls.httpRedirectPort).toBe(9080);
    expect(cfg.tls.selfSignedHosts).toEqual([]);
  });

  it('throws when http_redirect_port equals the https port', () => {
    expect(() =>
      mergeServerConfig({ tls: { enabled: true, httpRedirectPort: 9876 } }, { freshInstall: false, httpsPort: 9876 }),
    ).toThrow(/redirect.*port/i);
  });

  it('throws when only one of cert_file/key_file is set', () => {
    expect(() =>
      mergeServerConfig({ tls: { enabled: true, certFile: '/x/cert.pem' } }, { freshInstall: false }),
    ).toThrow(/cert_file.*key_file|both/i);
  });

  it('throws when key_file is set without cert_file', () => {
    expect(() =>
      mergeServerConfig({ tls: { enabled: true, keyFile: '/x/key.pem' } }, { freshInstall: false }),
    ).toThrow(/cert_file.*key_file|both/i);
  });

  it('does not throw on port collision when httpRedirect is false', () => {
    expect(() =>
      mergeServerConfig(
        { tls: { enabled: true, httpRedirect: false, httpRedirectPort: 9876 } },
        { freshInstall: false, httpsPort: 9876 },
      ),
    ).not.toThrow();
  });

  it('disabled TLS with a lone certFile does not throw', () => {
    expect(() =>
      mergeServerConfig({ tls: { enabled: false, certFile: '/x/c.pem' } }, { freshInstall: false }),
    ).not.toThrow();
  });

  it('resolves port: httpsPort wins, else config port, else default', () => {
    expect(mergeServerConfig({ port: 3000 }, { freshInstall: false }).port).toBe(3000);
    expect(mergeServerConfig({ port: 3000 }, { freshInstall: false, httpsPort: 8443 }).port).toBe(8443);
    expect(mergeServerConfig(undefined, { freshInstall: false }).port).toBe(DEFAULT_SERVER_PORT);
  });
});

describe('resolveListenPort', () => {
  it('PORT env wins over config port and default', () => {
    expect(resolveListenPort('4000', 5000)).toBe(4000);
  });

  it('falls back to config port when env is unset/empty', () => {
    expect(resolveListenPort(undefined, 5000)).toBe(5000);
    expect(resolveListenPort('', 5000)).toBe(5000);
  });

  it('falls back to the default when neither is set', () => {
    expect(resolveListenPort(undefined, undefined)).toBe(DEFAULT_SERVER_PORT);
  });

  it('ignores an invalid env PORT and warns, then uses config', () => {
    const warns: string[] = [];
    expect(resolveListenPort('not-a-port', 5000, (m) => warns.push(m))).toBe(5000);
    expect(resolveListenPort('70000', 5000, (m) => warns.push(m))).toBe(5000); // out of range
    expect(warns.length).toBe(2);
  });

  it('ignores an invalid config port and warns, then uses the default', () => {
    const warns: string[] = [];
    expect(resolveListenPort(undefined, 0, (m) => warns.push(m))).toBe(DEFAULT_SERVER_PORT);
    expect(resolveListenPort(undefined, 99999, (m) => warns.push(m))).toBe(DEFAULT_SERVER_PORT);
    expect(resolveListenPort(undefined, 1.5, (m) => warns.push(m))).toBe(DEFAULT_SERVER_PORT); // non-integer
    expect(warns.length).toBe(3);
  });

  it('accepts the boundary ports 1 and 65535', () => {
    expect(resolveListenPort('1', undefined)).toBe(1);
    expect(resolveListenPort('65535', undefined)).toBe(65535);
  });

  it('rejects env "0" and falls through', () => {
    expect(resolveListenPort('0', 5000)).toBe(5000);
    expect(resolveListenPort('0', undefined)).toBe(DEFAULT_SERVER_PORT);
  });

  it('accepts a quoted-string config port symmetrically with env', () => {
    expect(resolveListenPort(undefined, '5000' as unknown as number)).toBe(5000);
    expect(resolveListenPort(undefined, '' as unknown as number)).toBe(DEFAULT_SERVER_PORT);
    expect(resolveListenPort(undefined, 'abc' as unknown as number)).toBe(DEFAULT_SERVER_PORT);
  });
});
