const fs = require('fs');
const { execSync } = require('child_process');
const path = require('path');
const os = require('os');

// Package managers to test
const PACKAGE_MANAGERS = ['npm', 'pnpm'];

// Well-known dependencies to install
const TEST_DEPENDENCIES = ['lodash'];

const results = { passed: 0, failed: 0, tests: [] };

function test(name, fn) {
  try {
    const result = fn();
    results.tests.push({ name, status: result ? 'PASS' : 'FAIL', error: null });
    result ? results.passed++ : results.failed++;
  } catch (e) {
    results.tests.push({ name, status: 'ERROR', error: e.message });
    results.failed++;
  }
}

function exec(cmd, options = {}) {
  try {
    const output = execSync(cmd, {
      encoding: 'utf8',
      timeout: 120000, // 2 minutes
      ...options
    });
    return { success: true, output };
  } catch (e) {
    return { success: false, error: e.message, output: e.stdout || '' };
  }
}

function createTempDir(prefix) {
  return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
}

function cleanup(dir) {
  try {
    fs.rmSync(dir, { recursive: true, force: true });
  } catch (e) {
    // Ignore cleanup errors
  }
}

// npm-restrictive allow_write includes /tmp/**. Use cache/store paths under /tmp so
// bubblewrap can bind /tmp read-write without pre-creating ~/.npm or ~/.cache/pnpm.
function pmEnv(pm) {
  const base = path.join(os.tmpdir(), 'pmg-e2e');
  fs.mkdirSync(base, { recursive: true });

  const env = { ...process.env };
  env.npm_config_cache = path.join(base, 'npm-cache');
  fs.mkdirSync(env.npm_config_cache, { recursive: true });

  if (pm === 'pnpm') {
    env.PNPM_HOME = path.join(base, 'pnpm-home');
    env.npm_config_store_dir = path.join(base, 'pnpm-store');
    fs.mkdirSync(env.PNPM_HOME, { recursive: true });
    fs.mkdirSync(env.npm_config_store_dir, { recursive: true });
    env.PATH = `${env.PNPM_HOME}${path.delimiter}${env.PATH}`;
  }

  return env;
}

function testPackageManager(pm) {
  const testDir = createTempDir(`pmg-e2e-${pm}-`);
  const env = pmEnv(pm);
  console.log(`\n  Test directory: ${testDir}`);

  try {
    // Initialize project
    test(`${pm}: Initialize project`, () => {
      const initCmd = pm === 'npm' ? 'npm init -y' : 'pnpm init';
      const result = exec(initCmd, { cwd: testDir, env });
      if (!result.success) {
        console.log(`    ❌ FAIL: ${result.error}`);
        return false;
      }

      const packageJsonExists = fs.existsSync(path.join(testDir, 'package.json'));
      if (packageJsonExists) {
        console.log(`    ✅ PASS: Project initialized`);
        return true;
      }
      console.log(`    ❌ FAIL: package.json not created`);
      return false;
    });

    // Add dependencies
    const depsStr = TEST_DEPENDENCIES.join(', ');
    test(`${pm}: Add dependencies (${depsStr})`, () => {
      const depsList = TEST_DEPENDENCIES.join(' ');
      const addCmd = pm === 'npm'
        ? `npm install ${depsList}`
        : `pnpm add ${depsList}`;
      const result = exec(addCmd, { cwd: testDir, env });
      if (!result.success) {
        console.log(`    ❌ FAIL: ${result.error}`);
        return false;
      }

      // Verify dependencies were added to package.json
      const packageJson = JSON.parse(fs.readFileSync(path.join(testDir, 'package.json'), 'utf8'));
      const missingDeps = TEST_DEPENDENCIES.filter(
        dep => !packageJson.dependencies || !packageJson.dependencies[dep]
      );
      if (missingDeps.length === 0) {
        console.log(`    ✅ PASS: Dependencies added`);
        return true;
      }
      console.log(`    ❌ FAIL: Missing dependencies in package.json: ${missingDeps.join(', ')}`);
      return false;
    });

    // Verify node_modules exists
    test(`${pm}: Verify node_modules created`, () => {
      const nodeModulesExists = fs.existsSync(path.join(testDir, 'node_modules'));
      if (!nodeModulesExists) {
        console.log(`    ❌ FAIL: node_modules missing`);
        return false;
      }
      const missingDeps = TEST_DEPENDENCIES.filter(
        dep => !fs.existsSync(path.join(testDir, 'node_modules', dep))
      );
      if (missingDeps.length === 0) {
        console.log(`    ✅ PASS: node_modules and dependencies exist`);
        return true;
      }
      console.log(`    ❌ FAIL: Missing in node_modules: ${missingDeps.join(', ')}`);
      return false;
    });

    // Clean install (remove node_modules and reinstall)
    test(`${pm}: Clean install`, () => {
      // Remove node_modules
      const nodeModulesPath = path.join(testDir, 'node_modules');
      fs.rmSync(nodeModulesPath, { recursive: true, force: true });

      // Reinstall
      const installCmd = pm === 'npm' ? 'npm install' : 'pnpm install';
      const result = exec(installCmd, { cwd: testDir, env });
      if (!result.success) {
        console.log(`    ❌ FAIL: ${result.error}`);
        return false;
      }

      // Verify node_modules recreated with all dependencies
      const missingDeps = TEST_DEPENDENCIES.filter(
        dep => !fs.existsSync(path.join(testDir, 'node_modules', dep))
      );
      if (missingDeps.length === 0) {
        console.log(`    ✅ PASS: Clean install successful`);
        return true;
      }
      console.log(`    ❌ FAIL: Dependencies not reinstalled: ${missingDeps.join(', ')}`);
      return false;
    });

  } finally {
    cleanup(testDir);
    console.log(`  Cleaned up: ${testDir}`);
  }
}

// Main
console.log('=== PMG Package Manager E2E Tests ===\n');
console.log('This script tests basic npm/pnpm flows to ensure sandbox compatibility.\n');

for (const pm of PACKAGE_MANAGERS) {
  // Check if package manager is available
  const checkResult = exec(`which ${pm}`);
  if (!checkResult.success) {
    console.log(`--- Skipping ${pm} (not installed) ---`);
    continue;
  }

  console.log(`--- Testing ${pm.toUpperCase()} ---`);
  testPackageManager(pm);
}

// Summary
console.log('\n=== SUMMARY ===');
console.log(`Passed: ${results.passed}/${results.tests.length}`);
console.log(`Failed: ${results.failed}/${results.tests.length}`);

if (results.failed > 0) {
  console.log('\nFailed tests:');
  results.tests.filter(t => t.status !== 'PASS').forEach(t => {
    console.log(`  - ${t.name}: ${t.status} ${t.error || ''}`);
  });
  process.exit(1);
}

console.log('\nAll tests passed!');
