/**
 * Docker Security Module
 * Provides image digest verification to ensure container images match expected digests
 * Protects against supply chain attacks and malicious image replacements
 */

const fs = require('fs');
const path = require('path');
const https = require('https');
const Docker = require('dockerode');

const docker = new Docker();

const SECURITY_CONFIG_FILE = process.env.DOCKER_SECURITY_CONFIG || path.join(__dirname, 'docker-security-config.json');
const VERIFICATION_MODE = process.env.DOCKER_VERIFICATION_MODE || 'verify'; // strict | verify | permissive

class DockerSecurity {
  constructor() {
    this.config = this.loadConfig();
    this.mode = VERIFICATION_MODE;
    console.log(`[DockerSecurity] Initialized in ${this.mode} mode`);
  }

  /**
   * Load security configuration
   */
  loadConfig() {
    try {
      if (fs.existsSync(SECURITY_CONFIG_FILE)) {
        const data = fs.readFileSync(SECURITY_CONFIG_FILE, 'utf8');
        return JSON.parse(data);
      }
    } catch (error) {
      console.warn(`[DockerSecurity] Failed to load config: ${error.message}`);
    }

    // Default configuration
    return {
      trustedDigests: {},
      verificationMode: VERIFICATION_MODE,
      allowUnverified: true,
      updateTrustedOnPull: true
    };
  }

  /**
   * Save security configuration
   */
  saveConfig() {
    try {
      fs.writeFileSync(SECURITY_CONFIG_FILE, JSON.stringify(this.config, null, 2));
    } catch (error) {
      console.error(`[DockerSecurity] Failed to save config: ${error.message}`);
    }
  }

  /**
   * Get image digest from Docker
   * @param {string} imageName - Full image name with tag (e.g., "nginx:latest")
   * @returns {Promise<string>} Image digest (sha256:...)
   */
  async getImageDigest(imageName) {
    try {
      const image = docker.getImage(imageName);
      const inspect = await image.inspect();

      // RepoDigests contains the full image reference with digest
      // Example: ["nginx@sha256:abcd1234..."]
      if (inspect.RepoDigests && inspect.RepoDigests.length > 0) {
        const digestPart = inspect.RepoDigests[0].split('@')[1];
        return digestPart;
      }

      // If no RepoDigest, use the local Image ID
      // This happens with locally built images or images pulled before digests were tracked
      return inspect.Id;
    } catch (error) {
      throw new Error(`Failed to get image digest: ${error.message}`);
    }
  }

  /**
   * Fetch manifest from Docker registry
   * @param {string} imageName - Image name (e.g., "nginx:latest")
   * @returns {Promise<object>} Manifest data with digest
   */
  async fetchRegistryManifest(imageName) {
    // Parse image name
    const parts = imageName.split('/');
    let registry = 'registry-1.docker.io';
    let repository = imageName;
    let tag = 'latest';

    // Handle different image name formats
    if (imageName.includes(':')) {
      const tagSplit = imageName.split(':');
      tag = tagSplit[tagSplit.length - 1];
      repository = tagSplit.slice(0, -1).join(':');
    }

    // Handle custom registries
    if (parts.length > 2 || (parts.length === 2 && parts[0].includes('.'))) {
      registry = parts[0];
      repository = parts.slice(1).join('/').split(':')[0];
    } else if (parts.length === 1) {
      // Official Docker Hub images need 'library/' prefix
      repository = `library/${repository.split(':')[0]}`;
    } else {
      repository = repository.split(':')[0];
    }

    console.log(`[DockerSecurity] Fetching manifest for ${registry}/${repository}:${tag}`);

    return new Promise((resolve, reject) => {
      const isDockerHub = registry === 'registry-1.docker.io';
      const tokenUrl = isDockerHub
        ? `https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repository}:pull`
        : null;

      const fetchManifest = (token) => {
        const options = {
          hostname: registry,
          path: `/v2/${repository}/manifests/${tag}`,
          method: 'GET',
          headers: {
            'Accept': 'application/vnd.docker.distribution.manifest.v2+json',
          }
        };

        if (token) {
          options.headers['Authorization'] = `Bearer ${token}`;
        }

        const req = https.request(options, (res) => {
          let data = '';

          res.on('data', (chunk) => {
            data += chunk;
          });

          res.on('end', () => {
            if (res.statusCode === 200) {
              try {
                const manifest = JSON.parse(data);
                const digest = res.headers['docker-content-digest'];
                resolve({ manifest, digest });
              } catch (error) {
                reject(new Error(`Failed to parse manifest: ${error.message}`));
              }
            } else {
              reject(new Error(`Registry returned status ${res.statusCode}: ${data}`));
            }
          });
        });

        req.on('error', (error) => {
          reject(new Error(`Registry request failed: ${error.message}`));
        });

        req.end();
      };

      // Get auth token for Docker Hub
      if (isDockerHub) {
        https.get(tokenUrl, (res) => {
          let data = '';
          res.on('data', (chunk) => { data += chunk; });
          res.on('end', () => {
            try {
              const authData = JSON.parse(data);
              fetchManifest(authData.token);
            } catch (error) {
              reject(new Error(`Failed to get auth token: ${error.message}`));
            }
          });
        }).on('error', (error) => {
          reject(new Error(`Auth request failed: ${error.message}`));
        });
      } else {
        fetchManifest(null);
      }
    });
  }

  /**
   * Verify image digest against trusted digests
   * @param {string} imageName - Image name with tag
   * @param {string} actualDigest - Actual digest from pulled image
   * @returns {Promise<object>} Verification result
   */
  async verifyImageDigest(imageName, actualDigest) {
    const baseImageName = imageName.split(':')[0];
    const trustedDigest = this.config.trustedDigests[imageName] || this.config.trustedDigests[baseImageName];

    const result = {
      verified: false,
      mode: this.mode,
      imageName,
      actualDigest,
      trustedDigest: trustedDigest || null,
      action: 'unknown'
    };

    if (!trustedDigest) {
      // No trusted digest configured
      if (this.mode === 'strict') {
        result.verified = false;
        result.action = 'reject';
        result.reason = 'No trusted digest configured (strict mode)';
      } else {
        result.verified = true;
        result.action = 'accept';
        result.reason = 'No trusted digest configured (permissive mode)';

        if (this.config.updateTrustedOnPull) {
          this.config.trustedDigests[imageName] = actualDigest;
          this.saveConfig();
          console.log(`[DockerSecurity] Added trusted digest for ${imageName}`);
        }
      }
    } else if (actualDigest === trustedDigest) {
      // Digest matches
      result.verified = true;
      result.action = 'accept';
      result.reason = 'Digest matches trusted value';
    } else {
      // Digest mismatch
      if (this.mode === 'strict') {
        result.verified = false;
        result.action = 'reject';
        result.reason = 'Digest mismatch (strict mode)';
      } else if (this.mode === 'verify') {
        result.verified = false;
        result.action = 'warn';
        result.reason = 'Digest mismatch (verify mode - warning only)';
      } else {
        result.verified = true;
        result.action = 'accept';
        result.reason = 'Digest mismatch (permissive mode - accepted)';
      }
    }

    return result;
  }

  /**
   * Verify an image after pulling
   * @param {string} imageName - Image name with tag
   * @returns {Promise<object>} Verification result
   */
  async verifyPulledImage(imageName) {
    console.log(`[DockerSecurity] Verifying image: ${imageName}`);

    try {
      const actualDigest = await this.getImageDigest(imageName);
      const result = await this.verifyImageDigest(imageName, actualDigest);

      if (result.action === 'reject') {
        console.error(`[DockerSecurity] REJECTED: ${result.reason}`);
        throw new Error(`Image verification failed: ${result.reason}`);
      } else if (result.action === 'warn') {
        console.warn(`[DockerSecurity] WARNING: ${result.reason}`);
        console.warn(`[DockerSecurity]   Expected: ${result.trustedDigest}`);
        console.warn(`[DockerSecurity]   Actual:   ${result.actualDigest}`);
      } else {
        console.log(`[DockerSecurity] ACCEPTED: ${result.reason}`);
      }

      return result;
    } catch (error) {
      console.error(`[DockerSecurity] Verification error: ${error.message}`);

      if (this.mode === 'strict') {
        throw error;
      }

      return {
        verified: false,
        mode: this.mode,
        imageName,
        action: this.mode === 'permissive' ? 'accept' : 'warn',
        error: error.message,
        reason: `Verification error (${this.mode} mode)`
      };
    }
  }

  /**
   * Add or update trusted digest for an image
   * @param {string} imageName - Image name with tag
   * @param {string} digest - Trusted digest
   */
  setTrustedDigest(imageName, digest) {
    this.config.trustedDigests[imageName] = digest;
    this.saveConfig();
    console.log(`[DockerSecurity] Updated trusted digest for ${imageName}`);
  }

  /**
   * Remove trusted digest for an image
   * @param {string} imageName - Image name with tag
   */
  removeTrustedDigest(imageName) {
    delete this.config.trustedDigests[imageName];
    this.saveConfig();
    console.log(`[DockerSecurity] Removed trusted digest for ${imageName}`);
  }

  /**
   * Get all trusted digests
   */
  getTrustedDigests() {
    return { ...this.config.trustedDigests };
  }

  /**
   * Set verification mode
   * @param {string} mode - strict | verify | permissive
   */
  setMode(mode) {
    if (!['strict', 'verify', 'permissive'].includes(mode)) {
      throw new Error('Invalid mode. Must be: strict, verify, or permissive');
    }
    this.mode = mode;
    this.config.verificationMode = mode;
    this.saveConfig();
    console.log(`[DockerSecurity] Verification mode set to: ${mode}`);
  }

  /**
   * Get security status
   */
  getStatus() {
    return {
      mode: this.mode,
      trustedImagesCount: Object.keys(this.config.trustedDigests).length,
      configFile: SECURITY_CONFIG_FILE,
      updateTrustedOnPull: this.config.updateTrustedOnPull
    };
  }
}

// Singleton instance
const dockerSecurity = new DockerSecurity();

module.exports = dockerSecurity;
