#!/usr/bin/env node
'use strict';

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

// =============================================================================
// Constants & Configuration
// =============================================================================

/**
 * Ordered list of variable groups presented during the wizard flow.
 */
const GROUP_ORDER = [
  'Core Settings',
  'Database',
  'Session',
  'NVD API',
  'Ivanti Integration',
  'Atlas Integration',
  'Jira Integration',
  'CARD Integration',
  'GitLab Integration',
  'Frontend Settings'
];

/**
 * One-line description per group (max 120 characters each).
 */
const GROUP_DESCRIPTIONS = {
  'Core Settings': 'Server port, hostname, and CORS configuration',
  'Database': 'PostgreSQL connection string for persistent storage',
  'Session': 'Secret key for signing session cookies',
  'NVD API': 'National Vulnerability Database API key for CVE lookups',
  'Ivanti Integration': 'RiskSense platform credentials for vulnerability sync',
  'Atlas Integration': 'Atlas InfoSec API for action plan management',
  'Jira Integration': 'Jira Data Center for ticket creation and tracking',
  'CARD Integration': 'CARD asset ownership API for host lookups',
  'GitLab Integration': 'GitLab API for feedback submission (bug reports)',
  'Frontend Settings': 'React app API endpoint configuration'
};

/**
 * Groups that present a skip prompt before entering.
 */
const SKIPPABLE_GROUPS = [
  'NVD API',
  'Ivanti Integration',
  'Atlas Integration',
  'Jira Integration',
  'CARD Integration',
  'GitLab Integration'
];

/**
 * Variables whose values are masked in display (passwords, secrets, API keys, tokens).
 */
const SENSITIVE_VARS = [
  'SESSION_SECRET',
  'NVD_API_KEY',
  'IVANTI_API_KEY',
  'ATLAS_API_PASS',
  'JIRA_API_TOKEN',
  'JIRA_PAT',
  'CARD_API_PASS',
  'GITLAB_PAT',
  'DATABASE_URL'
];

/**
 * Complete registry of all 32 managed environment variables with metadata.
 * Each descriptor contains:
 *   name        — environment variable name
 *   group       — which GROUP_ORDER group it belongs to
 *   required    — whether a non-empty value is mandatory
 *   default     — factory default value (null if none; 'derived' handled at runtime)
 *   description — max 120 chars explaining what the variable controls
 *   docUrl      — URL or instruction for obtaining the value (null if N/A)
 *   sensitive   — whether to mask the value in display
 *   validator   — name of validation function to apply (null if none)
 */
const VARIABLE_DESCRIPTORS = [
  // --- Core Settings ---
  {
    name: 'PORT',
    group: 'Core Settings',
    required: true,
    default: '3001',
    description: 'TCP port the backend Express server listens on',
    docUrl: null,
    sensitive: false,
    validator: 'validatePort'
  },
  {
    name: 'API_HOST',
    group: 'Core Settings',
    required: true,
    default: 'localhost',
    description: 'Hostname or IP address the backend binds to',
    docUrl: null,
    sensitive: false,
    validator: null
  },
  {
    name: 'CORS_ORIGINS',
    group: 'Core Settings',
    required: true,
    default: null, // derived from frontend port at runtime
    description: 'Comma-separated list of allowed CORS origins for the backend',
    docUrl: null,
    sensitive: false,
    validator: 'validateCorsOrigins'
  },

  // --- Database ---
  {
    name: 'DATABASE_URL',
    group: 'Database',
    required: true,
    default: null, // derived from docker-compose.yml or fallback
    description: 'PostgreSQL connection string (or "sqlite" for SQLite mode)',
    docUrl: null,
    sensitive: true,
    validator: 'validateDatabaseUrl'
  },

  // --- Session ---
  {
    name: 'SESSION_SECRET',
    group: 'Session',
    required: true,
    default: null,
    description: 'Secret key for signing session cookies — generate with: openssl rand -base64 32',
    docUrl: null,
    sensitive: true,
    validator: 'validateSessionSecret'
  },

  // --- NVD API ---
  {
    name: 'NVD_API_KEY',
    group: 'NVD API',
    required: false,
    default: null,
    description: 'API key to increase NVD rate limit from 5 to 50 requests per 30 seconds',
    docUrl: 'https://nvd.nist.gov/developers/request-an-api-key',
    sensitive: true,
    validator: null
  },

  // --- Ivanti Integration ---
  {
    name: 'IVANTI_API_KEY',
    group: 'Ivanti Integration',
    required: false,
    default: null,
    description: 'RiskSense API key from your profile settings (does not expire like session cookies)',
    docUrl: 'https://platform4.risksense.com — Profile > API Keys',
    sensitive: true,
    validator: null
  },
  {
    name: 'IVANTI_CLIENT_ID',
    group: 'Ivanti Integration',
    required: false,
    default: '1550',
    description: 'RiskSense client/organization ID for API requests',
    docUrl: 'https://platform4.risksense.com — visible in URL after login',
    sensitive: false,
    validator: null
  },
  {
    name: 'IVANTI_FIRST_NAME',
    group: 'Ivanti Integration',
    required: false,
    default: null,
    description: 'First name of the service account user for Ivanti API authentication',
    docUrl: null,
    sensitive: false,
    validator: null
  },
  {
    name: 'IVANTI_LAST_NAME',
    group: 'Ivanti Integration',
    required: false,
    default: null,
    description: 'Last name of the service account user for Ivanti API authentication',
    docUrl: null,
    sensitive: false,
    validator: null
  },
  {
    name: 'IVANTI_BU_FILTER',
    group: 'Ivanti Integration',
    required: false,
    default: 'NTS-AEO-ACCESS-ENG,NTS-AEO-STEAM',
    description: 'Comma-separated BU values to sync from Ivanti into the local findings cache',
    docUrl: null,
    sensitive: false,
    validator: null
  },
  {
    name: 'IVANTI_MANAGED_BUS',
    group: 'Ivanti Integration',
    required: false,
    default: 'NTS-AEO-ACCESS-ENG,NTS-AEO-STEAM',
    description: 'Comma-separated BUs considered "managed" for drift classification in the archive',
    docUrl: null,
    sensitive: false,
    validator: null
  },
  {
    name: 'IVANTI_SKIP_TLS',
    group: 'Ivanti Integration',
    required: false,
    default: 'false',
    description: 'Set to true if behind Charter SSL inspection proxy (disables TLS cert verification)',
    docUrl: null,
    sensitive: false,
    validator: null
  },

  // --- Atlas Integration ---
  {
    name: 'ATLAS_API_URL',
    group: 'Atlas Integration',
    required: false,
    default: null,
    description: 'Base URL for the Atlas InfoSec API (e.g. https://atlas-infosec.caas.charterlab.com)',
    docUrl: 'https://atlas-infosec.caas.charterlab.com — API documentation',
    sensitive: false,
    validator: null
  },
  {
    name: 'ATLAS_API_USER',
    group: 'Atlas Integration',
    required: false,
    default: null,
    description: 'Service account username for Atlas API Basic Auth',
    docUrl: null,
    sensitive: false,
    validator: null
  },
  {
    name: 'ATLAS_API_PASS',
    group: 'Atlas Integration',
    required: false,
    default: null,
    description: 'Service account password for Atlas API Basic Auth',
    docUrl: null,
    sensitive: true,
    validator: null
  },
  {
    name: 'ATLAS_SKIP_TLS',
    group: 'Atlas Integration',
    required: false,
    default: 'false',
    description: 'Set to true if behind Charter SSL inspection proxy (disables TLS cert verification)',
    docUrl: null,
    sensitive: false,
    validator: null
  },

  // --- Jira Integration ---
  {
    name: 'JIRA_BASE_URL',
    group: 'Jira Integration',
    required: false,
    default: null,
    description: 'Base URL of the Jira Data Center instance (VPN or Charter Network required)',
    docUrl: 'Jira instance URL — requires VPN or Charter Network connection',
    sensitive: false,
    validator: null
  },
  {
    name: 'JIRA_AUTH_METHOD',
    group: 'Jira Integration',
    required: false,
    default: 'basic',
    description: 'Authentication method: "basic" for service account or "pat" for Personal Access Token',
    docUrl: null,
    sensitive: false,
    validator: null
  },
  {
    name: 'JIRA_API_USER',
    group: 'Jira Integration',
    required: false,
    default: null,
    description: 'Service account username for Jira Basic Auth (used when JIRA_AUTH_METHOD=basic)',
    docUrl: null,
    sensitive: false,
    validator: null
  },
  {
    name: 'JIRA_API_TOKEN',
    group: 'Jira Integration',
    required: false,
    default: null,
    description: 'Service account password/token for Jira Basic Auth',
    docUrl: null,
    sensitive: true,
    validator: null
  },
  {
    name: 'JIRA_PAT',
    group: 'Jira Integration',
    required: false,
    default: null,
    description: 'Personal Access Token for Jira (used when JIRA_AUTH_METHOD=pat, requires ATLSUP approval)',
    docUrl: 'PAT naming convention: Function - Team - ATLSUP-XXXXX',
    sensitive: true,
    validator: null
  },
  {
    name: 'JIRA_PROJECT_KEY',
    group: 'Jira Integration',
    required: false,
    default: null,
    description: 'Default Jira project key for creating issues from the dashboard',
    docUrl: null,
    sensitive: false,
    validator: null
  },
  {
    name: 'JIRA_ISSUE_TYPE',
    group: 'Jira Integration',
    required: false,
    default: 'Task',
    description: 'Default issue type when creating Jira tickets from the dashboard',
    docUrl: null,
    sensitive: false,
    validator: null
  },
  {
    name: 'JIRA_SKIP_TLS',
    group: 'Jira Integration',
    required: false,
    default: 'false',
    description: 'Set to true if behind Charter SSL inspection proxy (disables TLS cert verification)',
    docUrl: null,
    sensitive: false,
    validator: null
  },

  // --- CARD Integration ---
  {
    name: 'CARD_API_URL',
    group: 'CARD Integration',
    required: false,
    default: null,
    description: 'Base URL for the CARD asset ownership API (card.charter.com or staging)',
    docUrl: 'https://card.charter.com — service account must be onboarded with CARD team',
    sensitive: false,
    validator: null
  },
  {
    name: 'CARD_API_USER',
    group: 'CARD Integration',
    required: false,
    default: null,
    description: 'Service account username for CARD API OAuth token acquisition',
    docUrl: null,
    sensitive: false,
    validator: null
  },
  {
    name: 'CARD_API_PASS',
    group: 'CARD Integration',
    required: false,
    default: null,
    description: 'Service account password for CARD API OAuth token acquisition',
    docUrl: null,
    sensitive: true,
    validator: null
  },
  {
    name: 'CARD_SKIP_TLS',
    group: 'CARD Integration',
    required: false,
    default: 'false',
    description: 'Set to true if behind Charter SSL inspection proxy (disables TLS cert verification)',
    docUrl: null,
    sensitive: false,
    validator: null
  },

  // --- GitLab Integration ---
  {
    name: 'GITLAB_URL',
    group: 'GitLab Integration',
    required: false,
    default: 'http://steam-gitlab.charterlab.com',
    description: 'Base URL of the GitLab instance for feedback submission',
    docUrl: null,
    sensitive: false,
    validator: null
  },
  {
    name: 'GITLAB_PROJECT_ID',
    group: 'GitLab Integration',
    required: false,
    default: null,
    description: 'Numeric project ID from GitLab project settings (Settings > General)',
    docUrl: 'GitLab project > Settings > General — numeric Project ID',
    sensitive: false,
    validator: null
  },
  {
    name: 'GITLAB_PAT',
    group: 'GitLab Integration',
    required: false,
    default: null,
    description: 'GitLab Personal Access Token with "api" scope for creating issues',
    docUrl: 'GitLab > Preferences > Access Tokens — requires "api" scope',
    sensitive: true,
    validator: null
  },

  // --- Frontend Settings ---
  {
    name: 'REACT_APP_API_BASE',
    group: 'Frontend Settings',
    required: true,
    default: null, // derived from PORT at runtime
    description: 'Full URL to the backend API including /api path (used by React fetch calls)',
    docUrl: null,
    sensitive: false,
    validator: null
  },
  {
    name: 'REACT_APP_API_HOST',
    group: 'Frontend Settings',
    required: true,
    default: null, // derived from PORT at runtime
    description: 'Backend host URL without /api path (used for direct file/download URLs)',
    docUrl: null,
    sensitive: false,
    validator: null
  }
];

// =============================================================================
// Parsing Functions
// =============================================================================

/**
 * Resolves shell variable substitution syntax ${VAR:-default} by extracting
 * the default value. Returns the original string if the pattern is not found.
 *
 * @param {string} str — input string potentially containing ${VAR:-default}
 * @returns {string} — the extracted default value, or the original string
 */
function resolveShellDefault(str) {
  const match = str.match(/\$\{[^:}]+:-([^}]+)\}/);
  return match ? match[1] : str;
}

/**
 * Parses a docker-compose.yml file to extract Postgres service configuration.
 * Uses a simple line-by-line state machine to find the postgres service and
 * extract POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, and the host port.
 *
 * @param {string} filePath — path to docker-compose.yml
 * @returns {{ user: string, password: string, database: string, port: string } | null}
 */
function parseDockerCompose(filePath) {
  let content;
  try {
    content = fs.readFileSync(filePath, 'utf8');
  } catch (e) {
    return null;
  }

  const lines = content.split('\n');

  // State machine states
  let inServices = false;
  let inPostgres = false;
  let inEnvironment = false;
  let inPorts = false;
  let postgresIndent = -1;
  let sectionIndent = -1;

  let user = null;
  let password = null;
  let database = null;
  let port = null;

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const trimmed = line.trimStart();
    const indent = line.length - trimmed.length;

    // Look for services: block
    if (trimmed === 'services:' || trimmed.startsWith('services:')) {
      inServices = true;
      continue;
    }

    if (!inServices) continue;

    // Look for postgres service (a line like "  postgres:" under services)
    if (!inPostgres) {
      if (indent > 0 && /^postgres\s*:/.test(trimmed)) {
        inPostgres = true;
        postgresIndent = indent;
        continue;
      }
      continue;
    }

    // If we're in the postgres service, check if we've left it
    // (a line at the same or lesser indent that isn't blank)
    if (trimmed.length > 0 && indent <= postgresIndent) {
      // We've exited the postgres service block
      break;
    }

    // Look for environment: or ports: section within postgres
    if (!inEnvironment && !inPorts) {
      if (/^environment\s*:/.test(trimmed)) {
        inEnvironment = true;
        sectionIndent = indent;
        continue;
      }
      if (/^ports\s*:/.test(trimmed)) {
        inPorts = true;
        sectionIndent = indent;
        continue;
      }
      continue;
    }

    // Inside environment section
    if (inEnvironment) {
      // Check if we've left the environment section
      if (trimmed.length > 0 && indent <= sectionIndent) {
        inEnvironment = false;
        // Check if this line starts a new section
        if (/^ports\s*:/.test(trimmed)) {
          inPorts = true;
          sectionIndent = indent;
          continue;
        }
        continue;
      }

      // Parse environment variables (format: KEY: value or KEY: ${VAR:-default})
      const envMatch = trimmed.match(/^(POSTGRES_\w+)\s*:\s*(.+)$/);
      if (envMatch) {
        const key = envMatch[1];
        let value = envMatch[2].trim();
        // Remove surrounding quotes if present
        if ((value.startsWith('"') && value.endsWith('"')) ||
            (value.startsWith("'") && value.endsWith("'"))) {
          value = value.slice(1, -1);
        }
        value = resolveShellDefault(value);

        if (key === 'POSTGRES_USER') user = value;
        else if (key === 'POSTGRES_PASSWORD') password = value;
        else if (key === 'POSTGRES_DB') database = value;
      }
      continue;
    }

    // Inside ports section
    if (inPorts) {
      // Check if we've left the ports section
      if (trimmed.length > 0 && indent <= sectionIndent) {
        inPorts = false;
        // Check if this line starts a new section
        if (/^environment\s*:/.test(trimmed)) {
          inEnvironment = true;
          sectionIndent = indent;
          continue;
        }
        continue;
      }

      // Parse port mapping (format: - "host:container" or - host:container)
      const portMatch = trimmed.match(/^-\s*"?(\d+)\s*:\s*\d+"?/);
      if (portMatch) {
        port = portMatch[1];
      }
      continue;
    }
  }

  // Return null if we couldn't extract all required values
  if (!user || !password || !database || !port) {
    return null;
  }

  return { user, password, database, port };
}

/**
 * Set of all managed variable names for O(1) lookup during env file parsing.
 */
const MANAGED_VARIABLE_NAMES = new Set(VARIABLE_DESCRIPTORS.map(d => d.name));

/**
 * Parses a .env file, separating managed variables from unmanaged lines.
 *
 * For each non-empty line:
 *   - Lines starting with '#' are treated as comments
 *   - Other lines are split on the first '=' to extract key and value
 *   - Surrounding double quotes are stripped from the value if present
 *   - If the key matches a managed variable name, it goes into the managed Map
 *   - Otherwise, the raw line (and any preceding comment lines) go into unmanaged
 *
 * @param {string} filePath — path to the .env file
 * @returns {{ managed: Map<string, string>, unmanaged: string[] }}
 */
function parseEnvFile(filePath) {
  try {
    const content = fs.readFileSync(filePath, 'utf8');
    const lines = content.split('\n');
    const managed = new Map();
    const unmanaged = [];
    const pendingComments = [];

    for (const line of lines) {
      // Skip empty lines
      if (line.trim() === '') {
        // Flush pending comments as unmanaged since they aren't attached to a variable
        if (pendingComments.length > 0) {
          unmanaged.push(...pendingComments);
          pendingComments.length = 0;
        }
        continue;
      }

      // Comment line
      if (line.trimStart().startsWith('#')) {
        pendingComments.push(line);
        continue;
      }

      // Key=value line — split on first '='
      const eqIndex = line.indexOf('=');
      if (eqIndex === -1) {
        // Malformed line — treat as unmanaged
        if (pendingComments.length > 0) {
          unmanaged.push(...pendingComments);
          pendingComments.length = 0;
        }
        unmanaged.push(line);
        continue;
      }

      const key = line.substring(0, eqIndex).trim();
      let value = line.substring(eqIndex + 1);

      // Strip surrounding double quotes from value
      if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
        value = value.slice(1, -1);
      }

      if (MANAGED_VARIABLE_NAMES.has(key)) {
        // Managed variable — discard preceding comments (they're group headers)
        pendingComments.length = 0;
        managed.set(key, value);
      } else {
        // Unmanaged variable — preserve preceding comments
        if (pendingComments.length > 0) {
          unmanaged.push(...pendingComments);
          pendingComments.length = 0;
        }
        unmanaged.push(line);
      }
    }

    // Flush any trailing pending comments
    if (pendingComments.length > 0) {
      unmanaged.push(...pendingComments);
    }

    return { managed, unmanaged };
  } catch (err) {
    // File doesn't exist or can't be read
    return { managed: new Map(), unmanaged: [] };
  }
}

// =============================================================================
// Validation Functions
// =============================================================================

/**
 * Validates that the value is a valid TCP port number (integer between 1 and 65535).
 * Leading and trailing whitespace is trimmed before validation.
 *
 * @param {string} value — user-provided port value
 * @returns {{ valid: boolean, message?: string }}
 */
function validatePort(value) {
  const trimmed = value.trim();
  const num = Number(trimmed);
  if (!Number.isInteger(num) || num < 1 || num > 65535) {
    return { valid: false, message: 'PORT must be an integer between 1 and 65535' };
  }
  return { valid: true };
}

/**
 * Validates that each comma-separated CORS origin starts with http:// or https://.
 * Whitespace is trimmed from each entry before validation.
 *
 * @param {string} value — comma-separated list of origins
 * @returns {{ valid: boolean, message?: string }}
 */
function validateCorsOrigins(value) {
  const entries = value.split(',').map(entry => entry.trim());
  for (const entry of entries) {
    if (!entry.startsWith('http://') && !entry.startsWith('https://')) {
      return { valid: false, message: 'Each CORS origin must start with http:// or https://' };
    }
  }
  return { valid: true };
}

/**
 * Validates that the DATABASE_URL starts with postgresql:// or equals "sqlite".
 *
 * @param {string} value — database connection string
 * @returns {{ valid: boolean, message?: string }}
 */
function validateDatabaseUrl(value) {
  if (value.startsWith('postgresql://') || value === 'sqlite') {
    return { valid: true };
  }
  return { valid: false, message: 'DATABASE_URL must start with postgresql:// or be "sqlite"' };
}

/**
 * Validates that the SESSION_SECRET is at least 16 characters long.
 *
 * @param {string} value — session secret value
 * @returns {{ valid: boolean, message?: string }}
 */
function validateSessionSecret(value) {
  if (value.length >= 16) {
    return { valid: true };
  }
  return { valid: false, message: 'SESSION_SECRET must be at least 16 characters long' };
}

/**
 * Validates that the value is non-empty and not whitespace-only.
 *
 * @param {string} value — user-provided value
 * @returns {{ valid: boolean, message?: string }}
 */
function validateRequired(value) {
  if (value.trim().length === 0) {
    return { valid: false, message: 'This field is required and cannot be empty' };
  }
  return { valid: true };
}

// =============================================================================
// Display Functions
// =============================================================================

/**
 * Prints a welcome message explaining the wizard's purpose.
 */
function printWelcome() {
  console.log('\nCVE Dashboard Configuration Wizard');
  console.log('===================================');
  console.log('This wizard will guide you through configuring backend and frontend');
  console.log('environment variables. Press Ctrl+C at any time to cancel.\n');
}

/**
 * Prints a group section header with its description from GROUP_DESCRIPTIONS.
 *
 * @param {string} group — the group name from GROUP_ORDER
 */
function printGroupHeader(group) {
  const description = GROUP_DESCRIPTIONS[group] || '';
  console.log(`\n=== ${group} ===`);
  console.log(description + '\n');
}

/**
 * Masks a sensitive variable value by showing only the first 4 and last 4
 * characters with asterisks in between. If the value is 8 characters or fewer,
 * returns the full value. If the variable name is not in SENSITIVE_VARS,
 * returns the value unchanged.
 *
 * @param {string} name — the variable name
 * @param {string} value — the variable value
 * @returns {string} — the masked or original value
 */
function maskSensitive(name, value) {
  if (!SENSITIVE_VARS.includes(name)) {
    return value;
  }
  if (value.length <= 8) {
    return value;
  }
  return value.slice(0, 4) + '****' + value.slice(-4);
}

/**
 * Displays a summary table organized by Variable_Group showing all configured
 * variables with their values. Sensitive variables are masked. Skipped groups
 * are excluded. Shows target file paths and whether each file already exists.
 *
 * @param {Map<string, string>} config — variable name → value
 * @param {Set<string>} skippedGroups — groups the user declined
 */
function printSummary(config, skippedGroups) {
  console.log('\n===================================');
  console.log('Configuration Summary');
  console.log('===================================\n');

  for (const group of GROUP_ORDER) {
    if (skippedGroups.has(group)) {
      continue;
    }

    const groupVars = VARIABLE_DESCRIPTORS.filter(
      d => d.group === group && config.has(d.name)
    );

    if (groupVars.length === 0) {
      continue;
    }

    console.log(`--- ${group} ---`);
    for (const descriptor of groupVars) {
      const value = config.get(descriptor.name);
      const displayValue = maskSensitive(descriptor.name, value);
      console.log(`  ${descriptor.name} = ${displayValue}`);
    }
    console.log('');
  }

  // Show skipped groups
  const skippedList = GROUP_ORDER.filter(g => skippedGroups.has(g));
  if (skippedList.length > 0) {
    console.log('Skipped groups:');
    for (const group of skippedList) {
      console.log(`  - ${group}`);
    }
    console.log('');
  }

  // Show target file paths and existence status
  const backendEnvPath = path.join('backend', '.env');
  const frontendEnvPath = path.join('frontend', '.env');
  const backendExists = fs.existsSync(backendEnvPath);
  const frontendExists = fs.existsSync(frontendEnvPath);

  console.log('Target files:');
  console.log(`  ${backendEnvPath} ${backendExists ? '(exists — will overwrite)' : '(new file)'}`);
  console.log(`  ${frontendEnvPath} ${frontendExists ? '(exists — will overwrite)' : '(new file)'}`);
  console.log('');
}

// =============================================================================
// Prompt Functions
// =============================================================================

/**
 * Map of validator function names to their implementations.
 * Used to look up validators by string name from variable descriptors.
 */
const VALIDATORS = {
  validatePort,
  validateCorsOrigins,
  validateDatabaseUrl,
  validateSessionSecret,
  validateRequired
};

/**
 * Prompts the user for a single variable value. Displays the variable label,
 * description, documentation URL, and default value. Validates input and
 * re-prompts on failure.
 *
 * @param {object} rl — readline interface
 * @param {object} descriptor — variable descriptor with name, description, etc.
 * @param {string|null} currentValue — existing value from a parsed env file
 * @returns {Promise<string>} — resolves with the final validated value
 */
function promptVariable(rl, descriptor, currentValue) {
  return new Promise((resolve) => {
    const ask = () => {
      // Line 1: [REQUIRED] or [OPTIONAL] label + variable name
      const label = descriptor.required ? '[REQUIRED]' : '[OPTIONAL]';
      console.log(`  ${label} ${descriptor.name}`);

      // Line 2: Description
      console.log(`  ${descriptor.description}`);

      // Line 3: Documentation URL (if present)
      if (descriptor.docUrl !== null) {
        console.log(`  Docs: ${descriptor.docUrl}`);
      }

      // Line 4: Default value display
      let defaultValue = null;
      let defaultLabel = '';
      if (currentValue !== null && currentValue !== undefined) {
        defaultValue = currentValue;
        defaultLabel = '[current]';
      } else if (descriptor.default !== null) {
        defaultValue = descriptor.default;
        defaultLabel = '';
      }

      if (defaultValue !== null) {
        let displayDefault = defaultValue;
        if (descriptor.sensitive) {
          displayDefault = maskSensitive(descriptor.name, defaultValue);
        }
        const labelSuffix = defaultLabel ? ` ${defaultLabel}` : '';
        console.log(`  (${displayDefault}${labelSuffix})`);
      }

      // Prompt
      rl.question('  > ', (answer) => {
        const trimmedAnswer = answer.trim();

        // If user enters empty string, use default
        if (trimmedAnswer === '') {
          if (defaultValue !== null) {
            resolve(defaultValue);
            return;
          }
          // No default exists — if required, validate and re-prompt
          if (descriptor.required) {
            const result = validateRequired('');
            console.log(`  Error: ${result.message}`);
            ask();
            return;
          }
          // Optional with no default — return empty string
          resolve('');
          return;
        }

        // Apply validator if one is specified
        if (descriptor.validator) {
          const validatorFn = VALIDATORS[descriptor.validator];
          if (validatorFn) {
            const result = validatorFn(trimmedAnswer);
            if (!result.valid) {
              console.log(`  Error: ${result.message}`);
              ask();
              return;
            }
          }
        }

        resolve(trimmedAnswer);
      });
    };

    ask();
  });
}

/**
 * Prompts the user with a yes/no question.
 *
 * @param {object} rl — readline interface
 * @param {string} question — the question to display
 * @param {boolean} defaultNo — if true, default is "no" ([y/N]); if false, default is "yes" ([Y/n])
 * @returns {Promise<boolean>} — resolves to true for yes, false for no
 */
function promptYesNo(rl, question, defaultNo) {
  return new Promise((resolve) => {
    const hint = defaultNo ? '[y/N]' : '[Y/n]';
    rl.question(`  ${question} ${hint} `, (answer) => {
      const trimmed = answer.trim().toLowerCase();
      if (trimmed === '') {
        resolve(!defaultNo);
        return;
      }
      if (trimmed === 'y' || trimmed === 'yes') {
        resolve(true);
        return;
      }
      if (trimmed === 'n' || trimmed === 'no') {
        resolve(false);
        return;
      }
      // Invalid input — use default
      resolve(!defaultNo);
    });
  });
}

/**
 * Prompts the user to confirm overwriting an existing file. Offers three options:
 * overwrite, create backup then overwrite, or skip.
 *
 * @param {object} rl — readline interface
 * @param {string} filePath — path to the existing file
 * @returns {Promise<string>} — resolves to 'overwrite', 'backup', or 'skip'
 */
function promptOverwrite(rl, filePath) {
  return new Promise((resolve) => {
    console.log(`\n  File already exists: ${filePath}`);
    console.log('  What would you like to do?');
    console.log('    1) Overwrite');
    console.log('    2) Create backup, then overwrite');
    console.log('    3) Skip (do not write this file)');

    rl.question('  > ', (answer) => {
      const trimmed = answer.trim();
      if (trimmed === '1') {
        resolve('overwrite');
      } else if (trimmed === '2') {
        resolve('backup');
      } else {
        resolve('skip');
      }
    });
  });
}

// =============================================================================
// File Writing Functions
// =============================================================================

/**
 * Determines whether a value needs to be wrapped in double quotes.
 * Values containing spaces, '#', or quote characters require quoting.
 *
 * @param {string} value — the variable value to check
 * @returns {boolean} — true if the value needs double-quote wrapping
 */
function needsQuoting(value) {
  return value.includes(' ') || value.includes('#') || value.includes('"') || value.includes("'");
}

/**
 * Generates the complete .env file content string from the provided configuration.
 *
 * For each group in groupOrder (that isn't in skippedGroups):
 *   - Writes a comment header: `# --- Group Name ---`
 *   - For each VARIABLE_DESCRIPTOR in that group:
 *     - If the variable has a value in the variables map, writes `KEY=value`
 *     - If the value contains spaces, `#`, or quote characters, wraps it in double quotes
 *     - If the variable is optional and has no value (not in the map), omits it
 *   - Adds a blank line between groups
 *
 * After all groups, if unmanagedLines has entries, adds a `# --- Custom Variables ---`
 * section and appends them.
 *
 * @param {Map<string, string>} variables — variable name → value
 * @param {string[]} groupOrder — array of group names in order
 * @param {object} groupDescriptions — group name → description (unused in output but kept for API consistency)
 * @param {string[]} unmanagedLines — raw lines to preserve in Custom Variables section
 * @param {Set<string>} skippedGroups — groups that were skipped by the user
 * @returns {string} — the complete .env file content
 */
function generateEnvContent(variables, groupOrder, groupDescriptions, unmanagedLines, skippedGroups) {
  const sections = [];

  for (const group of groupOrder) {
    if (skippedGroups.has(group)) {
      continue;
    }

    const groupVars = VARIABLE_DESCRIPTORS.filter(d => d.group === group);
    const lines = [];

    for (const descriptor of groupVars) {
      if (variables.has(descriptor.name)) {
        const value = variables.get(descriptor.name);
        if (needsQuoting(value)) {
          lines.push(`${descriptor.name}="${value}"`);
        } else {
          lines.push(`${descriptor.name}=${value}`);
        }
      } else {
        // Optional variable with no value — omit it
      }
    }

    // Only add the group section if it has at least one variable line
    if (lines.length > 0) {
      sections.push(`# --- ${group} ---\n${lines.join('\n')}`);
    }
  }

  let content = sections.join('\n\n');

  // Append unmanaged lines in a Custom Variables section
  if (unmanagedLines && unmanagedLines.length > 0) {
    if (content.length > 0) {
      content += '\n\n';
    }
    content += `# --- Custom Variables ---\n${unmanagedLines.join('\n')}`;
  }

  // Ensure file ends with a newline
  if (content.length > 0 && !content.endsWith('\n')) {
    content += '\n';
  }

  return content;
}

/**
 * Creates a timestamped backup copy of an existing file in the same directory.
 * The backup filename follows the pattern: {filename}.backup.{YYYYMMDD_HHmmss}
 *
 * @param {string} filePath — path to the file to back up
 * @returns {string} — the backup file path
 */
function createBackup(filePath) {
  const now = new Date();
  const year = now.getFullYear();
  const month = String(now.getMonth() + 1).padStart(2, '0');
  const day = String(now.getDate()).padStart(2, '0');
  const hours = String(now.getHours()).padStart(2, '0');
  const minutes = String(now.getMinutes()).padStart(2, '0');
  const seconds = String(now.getSeconds()).padStart(2, '0');
  const timestamp = `${year}${month}${day}_${hours}${minutes}${seconds}`;

  const backupPath = `${filePath}.backup.${timestamp}`;
  fs.copyFileSync(filePath, backupPath);
  return backupPath;
}

/**
 * Writes content to a file path. Returns a result object indicating success or failure.
 *
 * @param {string} filePath — path to write the file to
 * @param {string} content — the content to write
 * @returns {{ success: boolean, error?: string }} — result object
 */
function writeEnvFile(filePath, content) {
  try {
    fs.writeFileSync(filePath, content);
    return { success: true };
  } catch (err) {
    return { success: false, error: `Failed to write ${filePath}: ${err.message}` };
  }
}

// =============================================================================
// Main Flow
// =============================================================================

/**
 * Main entry point that orchestrates the entire wizard flow.
 * Validates project structure, parses existing config, prompts the user
 * through all variable groups, and writes the resulting env files.
 */
async function main() {
  const readline = require('readline');

  // -------------------------------------------------------------------------
  // 1. Validate project root
  // -------------------------------------------------------------------------
  const backendDir = path.join(process.cwd(), 'backend');
  const frontendDir = path.join(process.cwd(), 'frontend');

  if (!fs.existsSync(backendDir) || !fs.existsSync(frontendDir)) {
    console.error('Error: This script must be run from the project root (backend/ and frontend/ directories not found). Run from the directory containing both folders.');
    process.exit(1);
  }

  // -------------------------------------------------------------------------
  // 2. Parse existing env files for pre-filling
  // -------------------------------------------------------------------------
  const backendEnvPath = path.join('backend', '.env');
  const frontendEnvPath = path.join('frontend', '.env');

  const existingBackend = parseEnvFile(backendEnvPath);
  const existingFrontend = parseEnvFile(frontendEnvPath);

  // -------------------------------------------------------------------------
  // 3. Parse docker-compose.yml for DATABASE_URL default
  // -------------------------------------------------------------------------
  const composePath = path.join(process.cwd(), 'docker-compose.yml');
  const composeResult = parseDockerCompose(composePath);
  const FALLBACK_DATABASE_URL = 'postgresql://steam:sV4xmC9xAUCFop0ypxMVS056QgPqGrX@localhost:5433/cve_dashboard';

  let derivedDatabaseUrl = FALLBACK_DATABASE_URL;
  let databaseUrlSource = 'fallback';

  if (composeResult) {
    derivedDatabaseUrl = `postgresql://${composeResult.user}:${composeResult.password}@localhost:${composeResult.port}/${composeResult.database}`;
    databaseUrlSource = 'compose';
  }

  // -------------------------------------------------------------------------
  // 4. Set up readline and SIGINT handler
  // -------------------------------------------------------------------------
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });

  process.on('SIGINT', () => {
    rl.close();
    console.log('\n\nConfiguration cancelled. No files were written.');
    process.exit(1);
  });

  // -------------------------------------------------------------------------
  // 5. Display welcome
  // -------------------------------------------------------------------------
  let restart = true;

  while (restart) {
    restart = false;

    printWelcome();

    // -----------------------------------------------------------------------
    // 6. Group loop — prompt for each variable group
    // -----------------------------------------------------------------------
    const values = new Map();
    const skippedGroups = new Set();
    let confirmedPort = null;

    for (const group of GROUP_ORDER) {
      printGroupHeader(group);

      // For skippable groups, ask if user wants to configure
      if (SKIPPABLE_GROUPS.includes(group)) {
        const configure = await promptYesNo(rl, `Configure ${group}?`, true);
        if (!configure) {
          skippedGroups.add(group);
          console.log(`  Skipping ${group}.`);
          continue;
        }
      }

      // Get variables for this group
      const groupVars = VARIABLE_DESCRIPTORS.filter(d => d.group === group);

      for (const descriptor of groupVars) {
        // Determine the effective current value for pre-filling
        let currentValue = null;

        // Check existing env values first
        if (descriptor.group === 'Frontend Settings') {
          if (existingFrontend.managed.has(descriptor.name)) {
            currentValue = existingFrontend.managed.get(descriptor.name);
          }
        } else {
          if (existingBackend.managed.has(descriptor.name)) {
            currentValue = existingBackend.managed.get(descriptor.name);
          }
        }

        // If no current value, determine derived defaults for special variables
        if (currentValue === null && descriptor.default === null) {
          if (descriptor.name === 'DATABASE_URL') {
            currentValue = null; // Will use derived default below
          } else if (descriptor.name === 'CORS_ORIGINS') {
            if (confirmedPort !== null) {
              currentValue = null; // Will use derived default below
            }
          } else if (descriptor.name === 'REACT_APP_API_BASE') {
            if (confirmedPort !== null) {
              currentValue = null; // Will use derived default below
            }
          } else if (descriptor.name === 'REACT_APP_API_HOST') {
            if (confirmedPort !== null) {
              currentValue = null; // Will use derived default below
            }
          }
        }

        // Build a modified descriptor with the effective default for derived variables
        let effectiveDescriptor = descriptor;

        if (descriptor.name === 'DATABASE_URL' && currentValue === null && descriptor.default === null) {
          const sourceMsg = databaseUrlSource === 'compose'
            ? ' (auto-derived from docker-compose.yml)'
            : ' (fallback from deploy script — docker-compose.yml could not be used)';
          effectiveDescriptor = {
            ...descriptor,
            default: derivedDatabaseUrl,
            description: descriptor.description + sourceMsg
          };
        } else if (descriptor.name === 'CORS_ORIGINS' && currentValue === null && descriptor.default === null) {
          effectiveDescriptor = { ...descriptor, default: 'http://localhost:3000' };
        } else if (descriptor.name === 'REACT_APP_API_BASE' && currentValue === null && descriptor.default === null) {
          const port = confirmedPort || '3001';
          effectiveDescriptor = { ...descriptor, default: `http://localhost:${port}/api` };
        } else if (descriptor.name === 'REACT_APP_API_HOST' && currentValue === null && descriptor.default === null) {
          const port = confirmedPort || '3001';
          effectiveDescriptor = { ...descriptor, default: `http://localhost:${port}` };
        }

        const result = await promptVariable(rl, effectiveDescriptor, currentValue);

        // Store the result if non-empty (or if required)
        if (result !== '') {
          values.set(descriptor.name, result);
        }

        // Track confirmed PORT for deriving other defaults
        if (descriptor.name === 'PORT') {
          confirmedPort = result || '3001';
        }
      }

      // After Core Settings group completes, derive defaults for later groups
      if (group === 'Core Settings') {
        // CORS_ORIGINS default is already handled inline above
        // REACT_APP_API_BASE and REACT_APP_API_HOST defaults use confirmedPort
      }
    }

    // -----------------------------------------------------------------------
    // 7. Display summary
    // -----------------------------------------------------------------------
    printSummary(values, skippedGroups);

    // -----------------------------------------------------------------------
    // 8. Confirmation
    // -----------------------------------------------------------------------
    const confirmed = await promptYesNo(rl, 'Write these settings to disk?', false);

    if (!confirmed) {
      // Ask restart or exit
      const restartChoice = await promptYesNo(rl, 'Restart the wizard from the beginning?', true);
      if (restartChoice) {
        restart = true;
        continue;
      } else {
        rl.close();
        console.log('\nExiting without writing files.');
        process.exit(1);
      }
    }

    // -----------------------------------------------------------------------
    // 9. Handle existing files — prompt for overwrite/backup
    // -----------------------------------------------------------------------
    let writeBackend = true;
    let writeFrontend = true;

    if (fs.existsSync(backendEnvPath)) {
      const choice = await promptOverwrite(rl, backendEnvPath);
      if (choice === 'skip') {
        writeBackend = false;
      } else if (choice === 'backup') {
        const backupPath = createBackup(backendEnvPath);
        console.log(`  Backup created: ${backupPath}`);
      }
    }

    if (fs.existsSync(frontendEnvPath)) {
      const choice = await promptOverwrite(rl, frontendEnvPath);
      if (choice === 'skip') {
        writeFrontend = false;
      } else if (choice === 'backup') {
        const backupPath = createBackup(frontendEnvPath);
        console.log(`  Backup created: ${backupPath}`);
      }
    }

    // -----------------------------------------------------------------------
    // 10. Write files
    // -----------------------------------------------------------------------

    // Separate variables into backend and frontend
    const backendVars = new Map();
    const frontendVars = new Map();

    for (const [key, value] of values) {
      const descriptor = VARIABLE_DESCRIPTORS.find(d => d.name === key);
      if (descriptor && descriptor.group === 'Frontend Settings') {
        frontendVars.set(key, value);
      } else {
        backendVars.set(key, value);
      }
    }

    // Backend groups = all groups except Frontend Settings
    const backendGroups = GROUP_ORDER.filter(g => g !== 'Frontend Settings');
    const frontendGroups = ['Frontend Settings'];

    if (writeBackend) {
      const backendContent = generateEnvContent(
        backendVars,
        backendGroups,
        GROUP_DESCRIPTIONS,
        existingBackend.unmanaged,
        skippedGroups
      );
      const result = writeEnvFile(backendEnvPath, backendContent);
      if (!result.success) {
        console.error(`Error: ${result.error}`);
        rl.close();
        process.exit(1);
      }
    }

    if (writeFrontend) {
      const frontendContent = generateEnvContent(
        frontendVars,
        frontendGroups,
        GROUP_DESCRIPTIONS,
        existingFrontend.unmanaged,
        skippedGroups
      );
      const result = writeEnvFile(frontendEnvPath, frontendContent);
      if (!result.success) {
        console.error(`Error: ${result.error}`);
        rl.close();
        process.exit(1);
      }
    }

    // -----------------------------------------------------------------------
    // 11. Success message
    // -----------------------------------------------------------------------
    console.log('\nConfiguration complete!\n');
    console.log('Next steps:');
    console.log('  1. Run `node backend/setup.js` to initialize the database');
    console.log('  2. Start the servers with `./start-servers.sh`');
    console.log('');
  }

  // -------------------------------------------------------------------------
  // 12. Exit code 0
  // -------------------------------------------------------------------------
  rl.close();
  process.exit(0);
}

// =============================================================================
// Exports (conditional — only when required as a module, not when run directly)
// =============================================================================

if (typeof require !== 'undefined' && require.main !== module) {
  module.exports = {
    VARIABLE_DESCRIPTORS,
    GROUP_ORDER,
    GROUP_DESCRIPTIONS,
    SKIPPABLE_GROUPS,
    SENSITIVE_VARS,
    MANAGED_VARIABLE_NAMES,
    VALIDATORS,
    resolveShellDefault,
    parseDockerCompose,
    parseEnvFile,
    validatePort,
    validateCorsOrigins,
    validateDatabaseUrl,
    validateSessionSecret,
    validateRequired,
    printWelcome,
    printGroupHeader,
    printSummary,
    maskSensitive,
    promptVariable,
    promptYesNo,
    promptOverwrite,
    needsQuoting,
    generateEnvContent,
    createBackup,
    writeEnvFile,
    main
  };
}

// Run the wizard when executed directly
if (require.main === module) {
  main();
}
