import { describe, it, expect } from 'vitest';
import {
  DEFAULT_JAIL_CONFIG,
  generateJailConfig,
  generateFstabEntries,
  getControlPlaneBastillePlan,
  getDefaultJailConfigForProfile,
  validateJailConfig,
  JailConfig,
  WARDEN_BRIDGE_NAME,
  WARDEN_CONTROL_PLANE_JAIL_ID,
  WARDEN_CONTROL_PLANE_HOSTNAME,
  WARDEN_CONTROL_PLANE_IP,
  WARDEN_CONTROL_PLANE_RELEASE,
  WARDEN_GATEWAY_IP,
  WARDEN_SUBNET_PREFIX,
  WARDEN_SUBNET_CIDR,
  WARDEN_WORKER_IP_START,
  WARDEN_JAIL_PROFILES,
} from './jail-config.js';

describe('Jail Config', () => {
  describe('DEFAULT_JAIL_CONFIG', () => {
    it('should have valid default configuration', () => {
      const errors = validateJailConfig(DEFAULT_JAIL_CONFIG);
      expect(errors).toHaveLength(0);
    });

    it('should have correct jail name', () => {
      expect(DEFAULT_JAIL_CONFIG.name).toBe(WARDEN_CONTROL_PLANE_JAIL_ID);
    });

    it('should have mountpoints defined', () => {
      expect(DEFAULT_JAIL_CONFIG.mountpoints).toBeDefined();
      expect(DEFAULT_JAIL_CONFIG.mountpoints.length).toBeGreaterThan(0);
    });

    it('should default control-plane profile to thick vnet persistent', () => {
      expect(DEFAULT_JAIL_CONFIG.runtime).toBe('freebsd-jail');
      expect(DEFAULT_JAIL_CONFIG.role).toBe('control-plane');
      expect(DEFAULT_JAIL_CONFIG.provisioning).toBe('thick');
      expect(DEFAULT_JAIL_CONFIG.networking).toBe('vnet');
      expect(DEFAULT_JAIL_CONFIG.persistent).toBe(true);
    });

    it('should use the canonical control-plane network settings', () => {
      expect(DEFAULT_JAIL_CONFIG.hostname).toBe(WARDEN_CONTROL_PLANE_HOSTNAME);
      expect(DEFAULT_JAIL_CONFIG.ip).toBe(WARDEN_CONTROL_PLANE_IP);
      expect(DEFAULT_JAIL_CONFIG.bridge).toBe(WARDEN_BRIDGE_NAME);
      expect(DEFAULT_JAIL_CONFIG.gateway).toBe(WARDEN_GATEWAY_IP);
    });
  });

  describe('generateJailConfig', () => {
    it('should generate valid jail.conf format', () => {
      const config = generateJailConfig();

      expect(config).toContain(`${WARDEN_CONTROL_PLANE_JAIL_ID} {`);
      expect(config).toContain('host.hostname');
      expect(config).toContain('ip4.addr');
      expect(config).toContain('path');
      expect(config).toContain('}');
    });

    it('should include nullfs mountpoints', () => {
      const config = generateJailConfig();

      expect(config).toContain('nullfs');
      expect(config).toContain('/home/clawdie/clawdie-ai');
    });

    it('should include devfs mount', () => {
      const config = generateJailConfig();

      expect(config).toContain('mount.devfs');
      expect(config).toContain('devfs_ruleset');
    });

    it('should include resource limits', () => {
      const config = generateJailConfig();

      expect(config).toContain('memoryuse');
      expect(config).toContain('maxproc');
    });

    it('should include Warden profile comments', () => {
      const config = generateJailConfig();

      expect(config).toContain('# Warden runtime: freebsd-jail');
      expect(config).toContain('# Warden role: control-plane');
      expect(config).toContain('# Provisioning: thick');
      expect(config).toContain('# Networking: vnet');
      expect(config).toContain(`defaultrouter = "${WARDEN_GATEWAY_IP}"`);
    });

    it('should override default values', () => {
      const config = generateJailConfig({
        name: 'testjail',
        ip: '192.168.100.200',
      });

      expect(config).toContain('testjail {');
      expect(config).toContain('192.168.100.200');
    });
  });

  describe('generateFstabEntries', () => {
    it('should generate fstab entries for nullfs mounts', () => {
      const entries = generateFstabEntries(DEFAULT_JAIL_CONFIG);

      expect(entries.length).toBeGreaterThan(0);
      expect(entries[0]).toContain('/home/clawdie/clawdie-ai');
      expect(entries[0]).toContain('nullfs');
    });

    it('should skip non-nullfs mountpoints', () => {
      const config: JailConfig = {
        ...DEFAULT_JAIL_CONFIG,
        mountpoints: [
          {
            source: '/data',
            destination: '/data',
            type: 'nullfs',
          },
          {
            source: 'devfs',
            destination: '/dev',
            type: 'devfs',
          },
        ],
      };

      const entries = generateFstabEntries(config);

      expect(entries.length).toBe(1);
      expect(entries[0]).toContain('/data');
    });
  });

  describe('validateJailConfig', () => {
    it('should pass for valid configuration', () => {
      const errors = validateJailConfig(DEFAULT_JAIL_CONFIG);
      expect(errors).toHaveLength(0);
    });

    it('should fail for invalid jail name', () => {
      const errors = validateJailConfig({
        ...DEFAULT_JAIL_CONFIG,
        name: 'invalid name!',
      });

      expect(errors.length).toBeGreaterThan(0);
      expect(errors[0]).toContain('alphanumeric');
    });

    it('should fail for invalid hostname', () => {
      const errors = validateJailConfig({
        ...DEFAULT_JAIL_CONFIG,
        hostname: 'invalid hostname!',
      });

      expect(errors.length).toBeGreaterThan(0);
      expect(errors[0]).toContain('hostname');
    });

    it('should fail for invalid IP address', () => {
      const errors = validateJailConfig({
        ...DEFAULT_JAIL_CONFIG,
        ip: 'invalid-ip',
      });

      expect(errors.length).toBeGreaterThan(0);
      expect(errors[0]).toContain('IP address');
    });

    it('should fail for relative jail path', () => {
      const errors = validateJailConfig({
        ...DEFAULT_JAIL_CONFIG,
        path: 'relative/path',
      });

      expect(errors.length).toBeGreaterThan(0);
      expect(errors[0]).toContain('absolute');
    });

    it('should fail for mountpoint missing source', () => {
      const errors = validateJailConfig({
        ...DEFAULT_JAIL_CONFIG,
        mountpoints: [
          {
            source: '',
            destination: '/dest',
            type: 'nullfs',
          },
        ],
      });

      expect(errors.length).toBeGreaterThan(0);
      expect(errors[0]).toContain('source or destination');
    });

    it('should fail for relative nullfs source', () => {
      const errors = validateJailConfig({
        ...DEFAULT_JAIL_CONFIG,
        mountpoints: [
          {
            source: 'relative/path',
            destination: '/dest',
            type: 'nullfs',
          },
        ],
      });

      expect(errors.length).toBeGreaterThan(0);
      expect(errors[0]).toContain('absolute');
    });

    it('should fail for invalid provisioning mode', () => {
      const errors = validateJailConfig({
        ...DEFAULT_JAIL_CONFIG,
        provisioning: 'invalid' as JailConfig['provisioning'],
      });

      expect(errors).toContain('Invalid jail provisioning mode');
    });

    it('should fail for invalid networking mode', () => {
      const errors = validateJailConfig({
        ...DEFAULT_JAIL_CONFIG,
        networking: 'invalid' as JailConfig['networking'],
      });

      expect(errors).toContain('Invalid jail networking mode');
    });

    it('should fail for invalid runtime kind', () => {
      const errors = validateJailConfig({
        ...DEFAULT_JAIL_CONFIG,
        runtime: 'invalid' as JailConfig['runtime'],
      });

      expect(errors).toContain('Invalid Warden runtime kind');
    });
  });

  describe('WARDEN_JAIL_PROFILES', () => {
    it('should define control-plane as thick and vnet', () => {
      expect(WARDEN_JAIL_PROFILES.controlPlane.runtime).toBe('freebsd-jail');
      expect(WARDEN_JAIL_PROFILES.controlPlane.provisioning).toBe('thick');
      expect(WARDEN_JAIL_PROFILES.controlPlane.networking).toBe('vnet');
      expect(WARDEN_JAIL_PROFILES.controlPlane.persistent).toBe(true);
    });

    it('should define worker as thin and shared', () => {
      expect(WARDEN_JAIL_PROFILES.worker.runtime).toBe('freebsd-jail');
      expect(WARDEN_JAIL_PROFILES.worker.provisioning).toBe('thin');
      expect(WARDEN_JAIL_PROFILES.worker.networking).toBe('shared');
      expect(WARDEN_JAIL_PROFILES.worker.persistent).toBe(false);
    });

    it('should define networked worker as thin and vnet', () => {
      expect(WARDEN_JAIL_PROFILES.networkedWorker.runtime).toBe('freebsd-jail');
      expect(WARDEN_JAIL_PROFILES.networkedWorker.provisioning).toBe('thin');
      expect(WARDEN_JAIL_PROFILES.networkedWorker.networking).toBe('vnet');
      expect(WARDEN_JAIL_PROFILES.networkedWorker.persistent).toBe(false);
    });

    it('should define browser VM as image-backed linux vm', () => {
      expect(WARDEN_JAIL_PROFILES.browserVm.runtime).toBe('linux-vm');
      expect(WARDEN_JAIL_PROFILES.browserVm.role).toBe('browser-vm');
      expect(WARDEN_JAIL_PROFILES.browserVm.provisioning).toBe('image');
      expect(WARDEN_JAIL_PROFILES.browserVm.networking).toBe('vm-bridged');
      expect(WARDEN_JAIL_PROFILES.browserVm.persistent).toBe(true);
    });

    it('should build worker config from profile', () => {
      const config = getDefaultJailConfigForProfile('worker');

      expect(config.name).toBe('warden-worker');
      expect(config.provisioning).toBe('thin');
      expect(config.networking).toBe('shared');
      expect(config.persistent).toBe(false);
      expect(config.ip).toBe(`192.168.100.${WARDEN_WORKER_IP_START}`);
    });

    it('should build networked worker config from profile', () => {
      const config = getDefaultJailConfigForProfile('networkedWorker');

      expect(config.name).toBe('warden-networked-worker');
      expect(config.provisioning).toBe('thin');
      expect(config.networking).toBe('vnet');
      expect(config.persistent).toBe(false);
      expect(config.ip).toBe(`192.168.100.${WARDEN_WORKER_IP_START + 49}`);
    });

    it('should build browser VM config from profile', () => {
      const config = getDefaultJailConfigForProfile('browserVm');

      expect(config.name).toBe('warden-browser-vm');
      expect(config.runtime).toBe('linux-vm');
      expect(config.provisioning).toBe('image');
      expect(config.networking).toBe('vm-bridged');
      expect(config.persistent).toBe(true);
      expect(config.path).toBe('/vm/warden-browser-vm');
    });

    it('should expose the canonical Bastille provisioning plan', () => {
      const plan = getControlPlaneBastillePlan();

      expect(plan.jailName).toBe(WARDEN_CONTROL_PLANE_JAIL_ID);
      expect(plan.hostname).toBe(WARDEN_CONTROL_PLANE_HOSTNAME);
      expect(plan.bridge).toBe(WARDEN_BRIDGE_NAME);
      expect(plan.gateway).toBe(WARDEN_GATEWAY_IP);
      expect(plan.jailIp).toBe(WARDEN_CONTROL_PLANE_IP);
      expect(plan.subnet).toBe(WARDEN_SUBNET_CIDR);
      expect(plan.createArgs).toEqual([
        'create',
        '-T',
        '-B',
        '-g',
        WARDEN_GATEWAY_IP,
        WARDEN_CONTROL_PLANE_JAIL_ID,
        WARDEN_CONTROL_PLANE_RELEASE,
        `${WARDEN_CONTROL_PLANE_IP}/${WARDEN_SUBNET_PREFIX}`,
        WARDEN_BRIDGE_NAME,
      ]);
    });
  });

  describe('getControlPlaneBastillePlan', () => {
    it('should include an explicit gateway in the Bastille create args', () => {
      const plan = getControlPlaneBastillePlan();

      expect(plan.gateway).toBe(WARDEN_GATEWAY_IP);
      expect(plan.createArgs).toEqual([
        'create',
        '-T',
        '-B',
        '-g',
        WARDEN_GATEWAY_IP,
        WARDEN_CONTROL_PLANE_JAIL_ID,
        WARDEN_CONTROL_PLANE_RELEASE,
        `${WARDEN_CONTROL_PLANE_IP}/${WARDEN_SUBNET_PREFIX}`,
        WARDEN_BRIDGE_NAME,
      ]);
    });
  });
});
