/**
 * Docker Security Module Tests
 * Tests for image digest verification, security modes, and trusted digest management
 * 
 * Note: Tests that call getImageDigest() require a real Docker daemon running.
 * These are marked with .skip() and should be run as integration tests separately.
 */

const fs = require('fs');
const path = require('path');

// Test config file path
const TEST_CONFIG_FILE = path.join(__dirname, '../docker-security-config.test.json');

describe('DockerSecurity Module', () => {
  let dockerSecurity;

  beforeEach(() => {
    // Clean up test config
    if (fs.existsSync(TEST_CONFIG_FILE)) {
      fs.unlinkSync(TEST_CONFIG_FILE);
    }

    // Set test environment
    process.env.DOCKER_SECURITY_CONFIG = TEST_CONFIG_FILE;
    process.env.DOCKER_VERIFICATION_MODE = 'verify';

    // Reset modules to get fresh instance
    jest.resetModules();
    dockerSecurity = require('../src/security/docker-security');
  });

  afterEach(() => {
    // Clean up test config
    if (fs.existsSync(TEST_CONFIG_FILE)) {
      fs.unlinkSync(TEST_CONFIG_FILE);
    }
  });

  describe('Configuration Management', () => {
    test('should load default config when file does not exist', () => {
      const status = dockerSecurity.getStatus();
      expect(status.mode).toBe('verify');
      expect(status.trustedImagesCount).toBe(0);
    });

    test('should load existing config file', () => {
      const testConfig = {
        trustedDigests: {
          'nginx:latest': 'sha256:abc123'
        },
        verificationMode: 'strict',
        allowUnverified: false,
        updateTrustedOnPull: false
      };

      fs.writeFileSync(TEST_CONFIG_FILE, JSON.stringify(testConfig));

      // Force module reload
      jest.resetModules();
      const freshInstance = require('../src/security/docker-security');
      const status = freshInstance.getStatus();

      expect(status.trustedImagesCount).toBe(1);
    });

    test('should save config to disk', () => {
      dockerSecurity.setTrustedDigest('redis:alpine', 'sha256:def456');

      expect(fs.existsSync(TEST_CONFIG_FILE)).toBe(true);

      const savedConfig = JSON.parse(fs.readFileSync(TEST_CONFIG_FILE, 'utf8'));
      expect(savedConfig.trustedDigests['redis:alpine']).toBe('sha256:def456');
    });

    test('should handle corrupted config file gracefully', () => {
      fs.writeFileSync(TEST_CONFIG_FILE, 'INVALID JSON{{{');

      jest.resetModules();
      const freshInstance = require('../src/security/docker-security');
      const status = freshInstance.getStatus();

      // Should fall back to default config
      expect(status.trustedImagesCount).toBe(0);
    });

    test('should handle missing config file directory', () => {
      // Use a non-existent directory
      process.env.DOCKER_SECURITY_CONFIG = '/nonexistent/path/config.json';

      jest.resetModules();
      const freshInstance = require('../src/security/docker-security');
      const status = freshInstance.getStatus();

      // Should fall back to default config
      expect(status.mode).toBe('verify');
      expect(status.trustedImagesCount).toBe(0);
    });
  });

  describe('Trusted Digest Management', () => {
    test('should add trusted digest', () => {
      dockerSecurity.setTrustedDigest('postgres:15', 'sha256:trusted123');

      const digests = dockerSecurity.getTrustedDigests();
      expect(digests['postgres:15']).toBe('sha256:trusted123');
    });

    test('should update existing trusted digest', () => {
      dockerSecurity.setTrustedDigest('postgres:15', 'sha256:old123');
      dockerSecurity.setTrustedDigest('postgres:15', 'sha256:new456');

      const digests = dockerSecurity.getTrustedDigests();
      expect(digests['postgres:15']).toBe('sha256:new456');
    });

    test('should remove trusted digest', () => {
      dockerSecurity.setTrustedDigest('postgres:15', 'sha256:trusted123');
      dockerSecurity.removeTrustedDigest('postgres:15');

      const digests = dockerSecurity.getTrustedDigests();
      expect(digests['postgres:15']).toBeUndefined();
    });

    test('should return copy of trusted digests (immutable)', () => {
      dockerSecurity.setTrustedDigest('nginx:latest', 'sha256:abc123');

      const digests1 = dockerSecurity.getTrustedDigests();
      const digests2 = dockerSecurity.getTrustedDigests();

      // Modify copy
      digests1['nginx:latest'] = 'sha256:modified';

      // Original should be unchanged
      expect(digests2['nginx:latest']).toBe('sha256:abc123');
    });

    test('should persist trusted digests across operations', () => {
      dockerSecurity.setTrustedDigest('mysql:8', 'sha256:mysql123');
      dockerSecurity.setTrustedDigest('redis:alpine', 'sha256:redis456');

      const digests = dockerSecurity.getTrustedDigests();
      expect(Object.keys(digests)).toHaveLength(2);
      expect(digests['mysql:8']).toBe('sha256:mysql123');
      expect(digests['redis:alpine']).toBe('sha256:redis456');
    });

    test('should handle removal of non-existent digest', () => {
      dockerSecurity.removeTrustedDigest('nonexistent:latest');

      const digests = dockerSecurity.getTrustedDigests();
      expect(digests['nonexistent:latest']).toBeUndefined();
    });

    test('should handle multiple removals', () => {
      dockerSecurity.setTrustedDigest('img1:latest', 'sha256:aaa111');
      dockerSecurity.setTrustedDigest('img2:latest', 'sha256:bbb222');
      dockerSecurity.setTrustedDigest('img3:latest', 'sha256:ccc333');

      dockerSecurity.removeTrustedDigest('img1:latest');
      dockerSecurity.removeTrustedDigest('img3:latest');

      const digests = dockerSecurity.getTrustedDigests();
      expect(Object.keys(digests)).toHaveLength(1);
      expect(digests['img2:latest']).toBe('sha256:bbb222');
    });
  });

  describe('Verification Modes', () => {
    test('should set mode to strict', () => {
      dockerSecurity.setMode('strict');
      const status = dockerSecurity.getStatus();
      expect(status.mode).toBe('strict');
    });

    test('should set mode to verify', () => {
      dockerSecurity.setMode('verify');
      const status = dockerSecurity.getStatus();
      expect(status.mode).toBe('verify');
    });

    test('should set mode to permissive', () => {
      dockerSecurity.setMode('permissive');
      const status = dockerSecurity.getStatus();
      expect(status.mode).toBe('permissive');
    });

    test('should reject invalid mode', () => {
      expect(() => dockerSecurity.setMode('invalid'))
        .toThrow('Invalid mode');
    });

    test('should reject empty mode string', () => {
      expect(() => dockerSecurity.setMode(''))
        .toThrow('Invalid mode');
    });

    test('should reject null mode', () => {
      expect(() => dockerSecurity.setMode(null))
        .toThrow('Invalid mode');
    });

    test('should persist mode changes to config', () => {
      dockerSecurity.setMode('strict');

      const savedConfig = JSON.parse(fs.readFileSync(TEST_CONFIG_FILE, 'utf8'));
      expect(savedConfig.verificationMode).toBe('strict');
    });

    test('should allow mode changes multiple times', () => {
      dockerSecurity.setMode('strict');
      expect(dockerSecurity.getStatus().mode).toBe('strict');

      dockerSecurity.setMode('permissive');
      expect(dockerSecurity.getStatus().mode).toBe('permissive');

      dockerSecurity.setMode('verify');
      expect(dockerSecurity.getStatus().mode).toBe('verify');
    });
  });

  describe('Digest Verification Logic - Strict Mode', () => {
    beforeEach(() => {
      dockerSecurity.setMode('strict');
    });

    test('should reject image with no trusted digest in strict mode', async () => {
      const result = await dockerSecurity.verifyImageDigest(
        'nginx:latest',
        'sha256:actual123'
      );

      expect(result.verified).toBe(false);
      expect(result.action).toBe('reject');
      expect(result.reason).toContain('strict mode');
    });

    test('should accept image with matching digest', async () => {
      dockerSecurity.setTrustedDigest('nginx:latest', 'sha256:trusted123');

      const result = await dockerSecurity.verifyImageDigest(
        'nginx:latest',
        'sha256:trusted123'
      );

      expect(result.verified).toBe(true);
      expect(result.action).toBe('accept');
    });

    test('should reject image with mismatched digest in strict mode', async () => {
      dockerSecurity.setTrustedDigest('nginx:latest', 'sha256:trusted123');

      const result = await dockerSecurity.verifyImageDigest(
        'nginx:latest',
        'sha256:different456'
      );

      expect(result.verified).toBe(false);
      expect(result.action).toBe('reject');
      expect(result.actualDigest).toBe('sha256:different456');
      expect(result.trustedDigest).toBe('sha256:trusted123');
    });

    test('should include all relevant fields in verification result', async () => {
      dockerSecurity.setTrustedDigest('redis:alpine', 'sha256:expected999');

      const result = await dockerSecurity.verifyImageDigest(
        'redis:alpine',
        'sha256:actual888'
      );

      expect(result).toHaveProperty('verified');
      expect(result).toHaveProperty('mode');
      expect(result).toHaveProperty('imageName');
      expect(result).toHaveProperty('actualDigest');
      expect(result).toHaveProperty('trustedDigest');
      expect(result).toHaveProperty('action');
      expect(result).toHaveProperty('reason');
    });
  });

  describe('Digest Verification Logic - Verify Mode', () => {
    beforeEach(() => {
      dockerSecurity.setMode('verify');
      // Disable auto-update for predictable tests
      dockerSecurity.config.updateTrustedOnPull = false;
    });

    test('should warn on digest mismatch in verify mode', async () => {
      dockerSecurity.setTrustedDigest('nginx:latest', 'sha256:trusted123');

      const result = await dockerSecurity.verifyImageDigest(
        'nginx:latest',
        'sha256:different456'
      );

      expect(result.verified).toBe(false);
      expect(result.action).toBe('warn');
      expect(result.reason).toContain('verify mode');
    });

    test('should accept image with no trusted digest in verify mode', async () => {
      const result = await dockerSecurity.verifyImageDigest(
        'redis:alpine',
        'sha256:actual123'
      );

      expect(result.verified).toBe(true);
      expect(result.action).toBe('accept');
    });

    test('should accept matching digests', async () => {
      dockerSecurity.setTrustedDigest('postgres:15', 'sha256:match777');

      const result = await dockerSecurity.verifyImageDigest(
        'postgres:15',
        'sha256:match777'
      );

      expect(result.verified).toBe(true);
      expect(result.action).toBe('accept');
    });
  });

  describe('Digest Verification Logic - Permissive Mode', () => {
    beforeEach(() => {
      dockerSecurity.setMode('permissive');
      dockerSecurity.config.updateTrustedOnPull = false;
    });

    test('should accept image with mismatched digest in permissive mode', async () => {
      dockerSecurity.setTrustedDigest('nginx:latest', 'sha256:trusted123');

      const result = await dockerSecurity.verifyImageDigest(
        'nginx:latest',
        'sha256:different456'
      );

      expect(result.verified).toBe(true);
      expect(result.action).toBe('accept');
      expect(result.reason).toContain('permissive mode');
    });

    test('should accept any image without trusted digest', async () => {
      const result = await dockerSecurity.verifyImageDigest(
        'unknown:latest',
        'sha256:anything123'
      );

      expect(result.verified).toBe(true);
      expect(result.action).toBe('accept');
    });

    test('should accept matching digests', async () => {
      dockerSecurity.setTrustedDigest('mysql:8', 'sha256:match555');

      const result = await dockerSecurity.verifyImageDigest(
        'mysql:8',
        'sha256:match555'
      );

      expect(result.verified).toBe(true);
      expect(result.action).toBe('accept');
    });
  });

  describe('Auto-Update Trusted Digests', () => {
    test('should auto-add trusted digest on first pull', async () => {
      dockerSecurity.config.updateTrustedOnPull = true;

      const result = await dockerSecurity.verifyImageDigest(
        'newimage:latest',
        'sha256:first123'
      );

      expect(result.verified).toBe(true);

      const digests = dockerSecurity.getTrustedDigests();
      expect(digests['newimage:latest']).toBe('sha256:first123');
    });

    test('should not auto-update when disabled', async () => {
      dockerSecurity.config.updateTrustedOnPull = false;

      await dockerSecurity.verifyImageDigest(
        'newimage:latest',
        'sha256:first123'
      );

      const digests = dockerSecurity.getTrustedDigests();
      expect(digests['newimage:latest']).toBeUndefined();
    });

    test('should not overwrite existing trusted digest', async () => {
      dockerSecurity.config.updateTrustedOnPull = true;
      dockerSecurity.setTrustedDigest('existing:latest', 'sha256:original888');

      await dockerSecurity.verifyImageDigest(
        'existing:latest',
        'sha256:new999'
      );

      const digests = dockerSecurity.getTrustedDigests();
      expect(digests['existing:latest']).toBe('sha256:original888');
    });
  });

  describe('Status Reporting', () => {
    test('should return correct status', () => {
      dockerSecurity.setTrustedDigest('nginx:latest', 'sha256:abc123');
      dockerSecurity.setTrustedDigest('redis:alpine', 'sha256:def456');
      dockerSecurity.setMode('strict');

      const status = dockerSecurity.getStatus();

      expect(status.mode).toBe('strict');
      expect(status.trustedImagesCount).toBe(2);
      expect(status.configFile).toBe(TEST_CONFIG_FILE);
    });

    test('should report updateTrustedOnPull setting', () => {
      dockerSecurity.config.updateTrustedOnPull = true;

      const status = dockerSecurity.getStatus();
      expect(status.updateTrustedOnPull).toBe(true);
    });

    test('should reflect config changes in status', () => {
      dockerSecurity.setMode('permissive');
      dockerSecurity.setTrustedDigest('img1:latest', 'sha256:aaa');
      dockerSecurity.setTrustedDigest('img2:latest', 'sha256:bbb');
      dockerSecurity.setTrustedDigest('img3:latest', 'sha256:ccc');

      const status = dockerSecurity.getStatus();

      expect(status.mode).toBe('permissive');
      expect(status.trustedImagesCount).toBe(3);
    });
  });

  describe('Edge Cases', () => {
    test('should handle concurrent digest updates', () => {
      dockerSecurity.setTrustedDigest('image1:latest', 'sha256:aaa111');
      dockerSecurity.setTrustedDigest('image2:latest', 'sha256:bbb222');
      dockerSecurity.setTrustedDigest('image3:latest', 'sha256:ccc333');

      const digests = dockerSecurity.getTrustedDigests();

      expect(digests['image1:latest']).toBe('sha256:aaa111');
      expect(digests['image2:latest']).toBe('sha256:bbb222');
      expect(digests['image3:latest']).toBe('sha256:ccc333');
    });

    test('should handle empty digest string', async () => {
      const result = await dockerSecurity.verifyImageDigest(
        'nginx:latest',
        ''
      );

      expect(result.verified).toBe(true); // Permissive by default
    });

    test('should handle very long image names', async () => {
      const longImageName = 'registry.example.com/namespace/project/subproject/image:v1.2.3-beta-20261231';

      dockerSecurity.setTrustedDigest(longImageName, 'sha256:abc123');

      const result = await dockerSecurity.verifyImageDigest(
        longImageName,
        'sha256:abc123'
      );

      expect(result.verified).toBe(true);
      expect(result.imageName).toBe(longImageName);
    });

    test('should handle digest verification with null digest', async () => {
      const result = await dockerSecurity.verifyImageDigest(
        'nginx:latest',
        null
      );

      // Null digest should be accepted in permissive mode (default)
      expect(result.action).toBe('accept');
    });

    test('should handle image name with multiple colons', async () => {
      dockerSecurity.setTrustedDigest('registry.io:5000/app:v1', 'sha256:xyz789');

      const result = await dockerSecurity.verifyImageDigest(
        'registry.io:5000/app:v1',
        'sha256:xyz789'
      );

      expect(result.verified).toBe(true);
    });
  });
});
