import { ExTester } from 'vscode-extension-tester';
import { EnvironmentSettings } from './environmentSettings';
import path from 'path';
import fs from 'fs/promises';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { ReleaseQuality } from 'vscode-extension-tester/out/util/codeUtil';
import { expect } from 'chai';
import { log } from 'console';
import { orgLoginSfdxUrl, setAlias } from './system-operations/cliCommands';
import { createOrOverwriteFile, getVsixFilesFromDir } from './system-operations';
import { TestConfig, ExtensionConfig } from './core/types';
import { createDefaultTestConfig, validateTestConfig, normalizePath } from './core/helpers';
import { verifyAliasAndUserName } from './salesforce-components/authorization';
import { retryOperation } from './retryUtils';

// Set Ubuntu Chrome arguments immediately when module loads
if (process.platform === 'linux') {
  const uniqueId = `${Date.now()}-${process.pid}`;
  const tempDir = path.join(process.cwd(), 'test-resources', 'chrome-user-data', uniqueId);

  const ubuntuChromeArgs = [
    `--user-data-dir=${tempDir}`,
    '--no-sandbox',
    '--disable-dev-shm-usage',
    '--disable-web-security',
    '--disable-features=VizDisplayCompositor',
    '--disable-gpu',
    '--disable-software-rasterizer',
    '--disable-background-timer-throttling',
    '--disable-backgrounding-occluded-windows',
    '--disable-renderer-backgrounding',
    '--disable-field-trial-config',
    '--disable-ipc-flooding-protection',
    '--single-process',
    '--no-zygote'
  ].join(' ');

  process.env.VSCODE_EXTENSION_TESTER_CHROMEDRIVER_ARGS = ubuntuChromeArgs;
  process.env.CHROME_OPTIONS = ubuntuChromeArgs;
  process.env.CHROMIUM_FLAGS = ubuntuChromeArgs;

  log(`Early Ubuntu Chrome setup: ${ubuntuChromeArgs}`);
}

// Set Chrome-related environment variables early
process.env.CHROME_OPTIONS =
  '--no-sandbox --disable-dev-shm-usage --disable-gpu --remote-debugging-port=9222 --disable-web-security --disable-features=VizDisplayCompositor --single-process --no-zygote --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-renderer-backgrounding --user-data-dir=/tmp/chrome-user-data-' +
  Date.now();
process.env.CHROMIUM_FLAGS =
  '--no-sandbox --disable-dev-shm-usage --disable-gpu --remote-debugging-port=9222 --disable-web-security --disable-features=VizDisplayCompositor --single-process --no-zygote --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-renderer-backgrounding --user-data-dir=/tmp/chrome-user-data-' +
  Date.now();
process.env.CHROME_BIN = '/usr/bin/google-chrome';
process.env.CHROMIUM_BIN = '/usr/bin/google-chrome';

// Skip Husky installation during test setup
process.env.HUSKY_SKIP_INSTALL = '1';

// Set NPM to use legacy peer dependencies
process.env.NPM_CONFIG_LEGACY_PEER_DEPS = 'true';

// For Ubuntu CI environments
if (process.platform === 'linux') {
  // Set up virtual display for headless operation
  process.env.DISPLAY = ':99';
  process.env.CHROME_NO_SANDBOX = 'true';
  process.env.PUPPETEER_SKIP_CHROMIUM_DOWNLOAD = 'true';
  process.env.PUPPETEER_EXECUTABLE_PATH = '/usr/bin/google-chrome';

  // Additional Chrome flags for CI
  process.env.CHROME_DEVEL_SANDBOX = '/usr/lib/chromium-browser/chrome-sandbox';
}

class TestSetupAndRunner extends ExTester {
  protected static _exTester: TestSetupAndRunner;
  private testConfig: TestConfig;
  private spec: string | string[] | undefined;
  private static readonly MAX_RETRIES = 3;
  private static readonly RETRY_DELAY = 5000;

  constructor(testConfig?: Partial<TestConfig>, spec?: string | string[] | undefined) {
    log(`Init testConfig with testConfig: ${JSON.stringify(testConfig)}`);
    log(`Init testConfig with spec: ${spec}`);
    // Create config with defaults and overrides
    const config = createDefaultTestConfig(testConfig);

    // Validate config and set defaults for missing values
    validateTestConfig(config);

    super(config?.testResources, ReleaseQuality.Stable);
    this.testConfig = config;
    this.spec = spec;
  }

  /**
   * Helper method to log important test information using standard terminology
   */
  private logTestEnvironment(): void {
    log(`Setting up test environment with:`);
    log(`- Test Resources: ${this.testConfig.testResources}`);
    log(`- Extensions Folder: ${this.testConfig.extensionsFolder}`);
    log(`- VS Code Version: ${this.testConfig.codeVersion}`);

    // Log Chrome driver arguments if available
    const chromeDriverArgs = EnvironmentSettings.getInstance().chromeDriverArgs;
    if (chromeDriverArgs) {
      log(`- ChromeDriver Arguments: ${chromeDriverArgs}`);
    }

    // Log the throttle factor
    const throttleFactor = EnvironmentSettings.getInstance().throttleFactor;
    log(`- Throttle Factor: ${throttleFactor}`);
  }

  /**
   * Sets up Ubuntu-specific Chrome driver arguments to avoid user data directory conflicts
   */
  private async setupUbuntuChromeArgs(): Promise<void> {
    // Only apply Ubuntu-specific settings if we're on Linux and no custom args are already set
    if (process.platform !== 'linux') {
      console.log(`Not on Linux, skipping setupUbuntuChromeArgs: ${process.platform}`);
      return;
    }

    const existingArgs = EnvironmentSettings.getInstance().chromeDriverArgs;
    if (existingArgs) {
      log(`Chrome driver arguments already set: ${existingArgs}`);
      return;
    }

    // Generate a unique user data directory for this test run
    const uniqueId = `${Date.now()}-${process.pid}`;
    const tempDir = path.join(this.testConfig.testResources || 'test-resources', 'chrome-user-data', uniqueId);

    // Ensure the directory exists
    try {
      await fs.mkdir(tempDir, { recursive: true });
      log(`Created Chrome user data directory: ${tempDir}`);
    } catch (error) {
      log(`Warning: Could not create Chrome user data directory: ${error}`);
    }

    const ubuntuChromeArgs = [
      `--user-data-dir=${tempDir}`,
      '--no-sandbox',
      '--disable-dev-shm-usage',
      '--disable-web-security',
      '--disable-features=VizDisplayCompositor',
      '--disable-gpu',
      '--disable-software-rasterizer',
      '--disable-background-timer-throttling',
      '--disable-backgrounding-occluded-windows',
      '--disable-renderer-backgrounding',
      '--disable-field-trial-config',
      '--disable-ipc-flooding-protection',
      '--single-process',
      '--no-zygote'
    ].join(' ');

    // Set the environment variable for vscode-extension-tester to pick up
    process.env.VSCODE_EXTENSION_TESTER_CHROMEDRIVER_ARGS = ubuntuChromeArgs;

    // Also try other possible environment variables
    process.env.CHROME_OPTIONS = ubuntuChromeArgs;
    process.env.CHROMIUM_FLAGS = ubuntuChromeArgs;
    process.env.GOOGLE_CHROME_OPTS = ubuntuChromeArgs;

    log(`Set Ubuntu Chrome driver arguments: ${ubuntuChromeArgs}`);
    log(
      `Environment variable VSCODE_EXTENSION_TESTER_CHROMEDRIVER_ARGS: ${process.env.VSCODE_EXTENSION_TESTER_CHROMEDRIVER_ARGS}`
    );

    // Also try setting the user data dir in a more explicit way
    const userDataDir = tempDir;
    process.env.CHROME_USER_DATA_DIR = userDataDir;
    process.env.CHROMIUM_USER_DATA_DIR = userDataDir;
    log(`Set CHROME_USER_DATA_DIR: ${userDataDir}`);
  }

  /**
   * Kills existing Chrome processes that might be using the user data directory
   */
  private async killExistingChromeProcesses(): Promise<void> {
    try {
      const { exec } = await import('child_process');
      const { promisify } = await import('util');
      const execAsync = promisify(exec);

      log('Attempting to kill existing Chrome processes...');

      // Kill Chrome processes
      const commands = [
        'pkill -f "chrome.*--test-type"',
        'pkill -f "chromium.*--test-type"',
        'pkill -f "google-chrome.*--test-type"',
        'pkill -f "chrome.*--user-data-dir"',
        'pkill -f "chromium.*--user-data-dir"'
      ];

      for (const command of commands) {
        try {
          await execAsync(command);
          log(`Executed: ${command}`);
        } catch (error) {
          // It's normal for pkill to exit with code 1 if no processes are found
          log(`Command ${command} completed (processes may not have been running)`);
        }
      }

      // Wait a moment for processes to fully terminate
      await new Promise(resolve => setTimeout(resolve, 2000));
      log('Chrome process cleanup completed');
    } catch (error) {
      log(`Warning: Could not kill Chrome processes: ${error}`);
    }
  }

  /**
   * Kills existing VS Code processes on Windows to prevent file locking issues during extension installation
   */
  private async killExistingVSCodeProcesses(): Promise<void> {
    if (process.platform !== 'win32') {
      return;
    }

    try {
      const { exec } = await import('child_process');
      const { promisify } = await import('util');
      const execAsync = promisify(exec);

      log('Attempting to kill existing VS Code processes on Windows...');

      // Kill VS Code processes on Windows
      const commands = [
        'taskkill /f /im Code.exe',
        'taskkill /f /im code.exe',
        'taskkill /f /im VSCode.exe',
        'taskkill /f /im electron.exe /fi "WINDOWTITLE eq Visual Studio Code*"',
        'taskkill /f /im node.exe /fi "COMMANDLINE eq *vscode*"'
      ];

      for (const command of commands) {
        try {
          await execAsync(command);
          log(`Executed: ${command}`);
        } catch (error) {
          // It's normal for taskkill to exit with code 1 if no processes are found
          log(`Command ${command} completed (processes may not have been running)`);
        }
      }

      // Wait a moment for processes to fully terminate and release file locks
      await new Promise(resolve => setTimeout(resolve, 3000));
      log('VS Code process cleanup completed');
    } catch (error) {
      log(`Warning: Could not kill VS Code processes: ${error}`);
    }
  }

  /**
   * Cleans up Windows VS Code extension temporary files that may cause locking issues
   */
  private async cleanupWindowsExtensionTempFiles(): Promise<void> {
    if (process.platform !== 'win32') {
      return;
    }

    try {
      const { exec } = await import('child_process');
      const { promisify } = await import('util');
      const execAsync = promisify(exec);

      log('Cleaning up Windows VS Code extension temporary files...');

      // Clean up VS Code extension temporary files
      const cleanupCommands = [
        'del /f /q "%USERPROFILE%\\.vscode\\extensions\\*.vsctmp" 2>nul',
        'del /f /q "%USERPROFILE%\\.vscode\\extensions\\*.tmp" 2>nul',
        'rmdir /s /q "%TEMP%\\vscode-*" 2>nul',
        'rmdir /s /q "%TEMP%\\Code-*" 2>nul'
      ];

      for (const command of cleanupCommands) {
        try {
          await execAsync(command);
          log(`Executed cleanup: ${command}`);
        } catch (error) {
          // It's normal for cleanup commands to fail if files don't exist
          log(`Cleanup command completed: ${command}`);
        }
      }

      log('Windows extension temp file cleanup completed');
    } catch (error) {
      log(`Warning: Could not cleanup Windows extension temp files: ${error}`);
    }
  }

  /**
   * Creates a Chrome wrapper script that forces Ubuntu-specific arguments
   */
  private async createChromeWrapper(): Promise<void> {
    try {
      const { exec } = await import('child_process');
      const { promisify } = await import('util');
      const execAsync = promisify(exec);

      // Find Chrome executable
      const chromeCommands = [
        'which google-chrome',
        'which google-chrome-stable',
        'which chromium-browser',
        'which chromium'
      ];

      let chromeExePath = '';
      for (const command of chromeCommands) {
        try {
          const result = await execAsync(command);
          chromeExePath = result.stdout.trim();
          if (chromeExePath) {
            log(`Found Chrome at: ${chromeExePath}`);
            break;
          }
        } catch (error) {
          // Command failed, try next one
        }
      }

      if (!chromeExePath) {
        log('Warning: Could not find Chrome executable for wrapper');
        return;
      }

      // Create unique user data directory
      const uniqueId = `${Date.now()}-${process.pid}`;
      const tempDir = path.join(this.testConfig.testResources || 'test-resources', 'chrome-user-data', uniqueId);
      await fs.mkdir(tempDir, { recursive: true });

      // Create wrapper script
      const wrapperPath = path.join(this.testConfig.testResources || 'test-resources', 'chrome-wrapper.sh');
      const wrapperScript = `#!/bin/bash
# Chrome wrapper script for Ubuntu testing
exec "${chromeExePath}" \\
  --user-data-dir="${tempDir}" \\
  --no-sandbox \\
  --disable-dev-shm-usage \\
  --disable-web-security \\
  --disable-features=VizDisplayCompositor \\
  --disable-gpu \\
  --disable-software-rasterizer \\
  --disable-background-timer-throttling \\
  --disable-backgrounding-occluded-windows \\
  --disable-renderer-backgrounding \\
  --disable-field-trial-config \\
  --disable-ipc-flooding-protection \\
  --single-process \\
  --no-zygote \\
  "$@"
`;

      createOrOverwriteFile(wrapperPath, wrapperScript);
      await execAsync(`chmod +x "${wrapperPath}"`);

      // Override Chrome path in environment
      process.env.CHROME_BIN = wrapperPath;
      process.env.CHROMIUM_BIN = wrapperPath;
      process.env.GOOGLE_CHROME_BIN = wrapperPath;

      // Create wrapper scripts for common Chrome names in our own directory
      const wrapperDir = path.join(this.testConfig.testResources || 'test-resources', 'chrome-wrappers');
      await fs.mkdir(wrapperDir, { recursive: true });

      const chromeNames = ['google-chrome', 'google-chrome-stable', 'chromium-browser', 'chromium'];
      for (const name of chromeNames) {
        const nameWrapperPath = path.join(wrapperDir, name);
        createOrOverwriteFile(nameWrapperPath, wrapperScript);
        await execAsync(`chmod +x "${nameWrapperPath}"`);
        log(`Created ${name} wrapper at: ${nameWrapperPath}`);
      }

      // Prepend our wrapper directory to PATH so our wrappers are found first
      process.env.PATH = `${wrapperDir}:${process.env.PATH}`;
      log(`Prepended ${wrapperDir} to PATH`);

      log(`Created Chrome wrapper at: ${wrapperPath}`);
      log(`Set CHROME_BIN to: ${wrapperPath}`);
    } catch (error) {
      log(`Warning: Could not create Chrome wrapper: ${error}`);
    }
  }

  public async setup(): Promise<void> {
    // Log the test environment configuration
    this.logTestEnvironment();

    // Set Ubuntu-specific Chrome driver arguments if needed
    await this.setupUbuntuChromeArgs();

    // Kill any existing Chrome processes on Linux
    if (process.platform === 'linux') {
      await this.killExistingChromeProcesses();
    }

    await this.downloadCode(this.testConfig.codeVersion);
    await this.downloadChromeDriver(this.testConfig.codeVersion);

    // Create Chrome wrapper script for Ubuntu
    if (process.platform === 'linux') {
      await this.createChromeWrapper();
    }

    // Kill any existing VS Code processes on Windows before installing extensions
    if (process.platform === 'win32') {
      await this.killExistingVSCodeProcesses();
      await this.cleanupWindowsExtensionTempFiles();
    }

    try {
      await this.installExtensions();
    } catch (error: unknown) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      log(`Warning: Failed to install extensions: ${errorMessage}. Continuing setup.`);
    }
    await this.setupAndAuthorizeOrg();
  }

  public async runTests(): Promise<number> {
    const useExistingProject = EnvironmentSettings.getInstance().useExistingProject;
    const resources = useExistingProject ? [useExistingProject] : [];

    log(`starting runTests with useExistingProject: ${useExistingProject}`);
    log(`starting runTests with resources: ${resources}`);
    log(`starting runTests with spec: ${this.spec}`);

    // Set Ubuntu Chrome arguments just before running tests
    const isLinux = process.platform === 'linux';
    if (isLinux) {
      await this.setupUbuntuChromeArgs();
      await this.killExistingChromeProcesses();

      // Use Ubuntu-specific test runner
      log('Using Ubuntu-specific test runner...');
      return await this.runTestsUbuntu();
    }

    // Try to pass additional launch arguments for Ubuntu
    const runOptions: { resources: string[]; launchArgs?: string[] } = { resources };
    if (isLinux) {
      runOptions.launchArgs = [
        '--disable-web-security',
        '--disable-features=VizDisplayCompositor',
        '--no-sandbox',
        '--disable-dev-shm-usage'
      ];
    }

    const exitCode = await super.runTests(this.spec || EnvironmentSettings.getInstance().specFiles, runOptions);
    await this.validateTestExecution(exitCode);
    return exitCode;
  }
  public async installExtension(extension: string): Promise<void> {
    log(`SetUp - Started Install extension ${path.basename(extension)}`);
    await this.installVsix({ useYarn: false, vsixFile: extension });
  }

  /**
   * Installs a single VSIX file with platform-specific handling
   * @param vsixPath - Path to the VSIX file to install
   */
  private async installSingleVsix(vsixPath: string): Promise<void> {
    let resolvedVsixPath = path.resolve(vsixPath);

    // Avoid Windows paths being parsed as URLs by the tester
    if (process.platform === 'win32') {
      resolvedVsixPath = resolvedVsixPath.replace(/\\/g, '/');
      if (/^[a-zA-Z]:\//.test(resolvedVsixPath)) {
        resolvedVsixPath = '/' + resolvedVsixPath; // Prevent `new URL()` from treating it as a URL
      }
    }

    // Use retry logic for Windows to handle file locking issues
    if (process.platform === 'win32') {
      await retryOperation(
        async () => {
          await new Promise(resolve => setTimeout(resolve, TestSetupAndRunner.RETRY_DELAY));
          await this.installExtension(resolvedVsixPath);
        },
        TestSetupAndRunner.MAX_RETRIES,
        `Failed to install extension ${path.basename(resolvedVsixPath)} after retries due to Windows file locking issues`
      );
    } else {
      await this.installExtension(resolvedVsixPath);
    }
  }

  /**
   * Installs extensions from VSIX files
   * @param excludeExtensions - Array of extension IDs to exclude from installation
   * @param extensionConfigs - Optional array of extension configurations for validation and control
   */
  public async installExtensions(
    excludeExtensions: string[] = [],
    extensionConfigs?: ExtensionConfig[]
  ): Promise<void> {
    const vsixToInstallDir = this.testConfig.vsixToInstallDir || EnvironmentSettings.getInstance().vsixToInstallDir;
    log(`Installing extension from vsixToInstallDir: ${vsixToInstallDir}`);

    if (!vsixToInstallDir) {
      log(`No VSIX_TO_INSTALL directory specified, the tests will run without installing any extensions`);
      return;
    }

    // Check if the directory exists
    try {
      await fs.access(vsixToInstallDir);
    } catch (error) {
      throw new Error(`VSIX_TO_INSTALL directory does not exist or is not accessible: ${vsixToInstallDir}`);
    }

    // If extension configs are provided, check for already installed extensions
    if (extensionConfigs && extensionConfigs.length > 0) {
      const extensionPattern = /^(?<publisher>.+?)\.(?<extensionId>.+?)-(?<version>\d+\.\d+\.\d+)(?:\.\d+)*$/;
      const extensionsDirEntries = (await fs.readdir(vsixToInstallDir)).map(entry =>
        path.resolve(normalizePath(path.join(vsixToInstallDir, entry)))
      );

      const foundInstalledExtensions = await Promise.all(
        extensionsDirEntries
          .filter(async entry => {
            try {
              const stats = await fs.stat(entry);
              return stats.isDirectory();
            } catch (e) {
              log(`stat failed for file ${entry}`);
              return false;
            }
          })
          .map(entry => {
            const match = path.basename(entry).match(extensionPattern);
            if (match?.groups) {
              return {
                publisher: match.groups.publisher,
                extensionId: match.groups.extensionId,
                version: match.groups.version,
                path: entry
              };
            }
            return null;
          })
          .filter(Boolean)
          .filter(ext =>
            extensionConfigs.find(refExt => {
              return refExt.extensionId === ext?.extensionId;
            })
          )
      );

      if (
        foundInstalledExtensions.length > 0 &&
        foundInstalledExtensions.every(ext => extensionConfigs.find(refExt => refExt.extensionId === ext?.extensionId))
      ) {
        log(`Found the following pre-installed extensions in dir ${vsixToInstallDir}, skipping installation of vsix`);
        foundInstalledExtensions.forEach(ext => {
          log(`Extension ${ext?.extensionId} version ${ext?.version}`);
        });
        return;
      }
    }

    const extensionsVsixs = getVsixFilesFromDir(vsixToInstallDir);
    if (extensionsVsixs.length === 0) {
      log(`No vsix files were found in dir ${vsixToInstallDir}, skipping extension installation`);
      return;
    }

    log(`VSIX files count: ${extensionsVsixs.length} found in dir ${vsixToInstallDir}`);

    // Build the merged exclude list
    const mergeExcluded = Array.from(
      new Set([
        ...excludeExtensions,
        ...(extensionConfigs?.filter(ext => ext.shouldInstall === 'never').map(ext => ext.extensionId) || [])
      ])
    );

    // If extension configs are provided, use them for validation and filtering
    if (extensionConfigs && extensionConfigs.length > 0) {
      // Create a mutable copy of extension configs to update with vsixPath
      const extensionConfigsMap = new Map(extensionConfigs.map(ext => [ext.extensionId, { ...ext }]));

      // Match VSIX files against extension configs and update vsixPath
      extensionsVsixs.forEach(vsix => {
        const match = path.basename(vsix).match(/^(?<extension>.*?)(-(?<version>\d+\.\d+\.\d+))?\.vsix$/);
        if (match?.groups) {
          const { extension, version } = match.groups;
          const foundExtension = extensionConfigsMap.get(extension);
          if (foundExtension) {
            foundExtension.vsixPath = vsix;
            // Assign 'never' to this extension if its id is included in excludedExtensions
            foundExtension.shouldInstall = mergeExcluded.includes(foundExtension.extensionId)
              ? 'never'
              : foundExtension.shouldInstall || 'always';
            // If not installing, don't verify, otherwise use default value
            foundExtension.shouldVerifyActivation =
              foundExtension.shouldInstall === 'never' ? false : foundExtension.shouldVerifyActivation;
            log(`SetUp - Found extension ${extension} version ${version} with vsixPath ${foundExtension.vsixPath}`);
          } else {
            log(`SetUp - Extension ${extension} version ${version} not in configured extensions list`);
          }
        }
      });

      // Install extensions that have vsixPath and shouldInstall !== 'never'
      for (const extensionConfig of extensionConfigsMap.values()) {
        if (extensionConfig.vsixPath && extensionConfig.shouldInstall !== 'never') {
          await this.installSingleVsix(extensionConfig.vsixPath);
        }
      }
    } else {
      // No extension configs provided - install all VSIX files except excluded ones
      for (const vsixPath of extensionsVsixs) {
        // Extract extension ID from VSIX filename
        const match = path.basename(vsixPath).match(/^(?<extension>.*?)(-(?<version>\d+\.\d+\.\d+))?\.vsix$/);
        if (match?.groups) {
          const { extension, version } = match.groups;

          // Skip if this extension is in the exclude list
          if (mergeExcluded.includes(extension)) {
            log(`SetUp - Skipping excluded extension ${extension} version ${version}`);
            continue;
          }

          log(`SetUp - Installing extension ${extension} version ${version}`);
        }

        await this.installSingleVsix(vsixPath);
      }
    }
  }

  public async setupAndAuthorizeOrg() {
    const environmentSettings = EnvironmentSettings.getInstance();
    const devHubUserName = environmentSettings.devHubUserName;
    const devHubAliasName = environmentSettings.devHubAliasName;
    const SFDX_AUTH_URL = environmentSettings.sfdxAuthUrl;

    try {
      // First try to verify if the org with alias and username exists
      log('Checking if org with matching alias and username is already authenticated...');
      await verifyAliasAndUserName();
      log(`Org with alias ${devHubAliasName} and username ${devHubUserName} is already authenticated`);
      return;
    } catch (error) {
      // If verification fails, continue with SFDX_AUTH_URL
      const errorMessage = error instanceof Error ? error.message : String(error);
      log(`No existing authenticated org found: ${errorMessage}`);

      // If no SFDX_AUTH_URL is provided, rethrow the error
      if (!SFDX_AUTH_URL) {
        throw new Error('No SFDX_AUTH_URL provided and no existing authentication found. Unable to authorize org.');
      }
    }

    // The verifyAliasAndUserName function already checks that both alias and username are set,
    // so at this point we know they are defined (or we would have thrown earlier).
    // TypeScript doesn't know this though, so we'll add the non-null assertion
    if (!devHubUserName || !devHubAliasName) {
      throw new Error(
        'DevHub username or alias name is not set. This should not happen as verifyAliasAndUserName should have caught this.'
      );
    }

    // At this point, neither alias nor username matched an existing org,
    // and we have an SFDX_AUTH_URL to use
    log('Authenticating using SFDX_AUTH_URL from environment variable...');

    // Step 1: Authorize to Testing Org
    const authorizeOrg = await orgLoginSfdxUrl();
    expect(authorizeOrg.stdout).to.contain(`Successfully authorized ${devHubUserName}`);
    log(`Successfully authorized ${devHubUserName}`);

    // Step 2: Set Alias for the Org
    const setAliasResult = await setAlias(devHubAliasName, devHubUserName);
    expect(setAliasResult.stdout).to.contain(devHubAliasName);
    expect(setAliasResult.stdout).to.contain(devHubUserName);
    expect(setAliasResult.stdout).to.contain('true');
    log(`Successfully setAliasResult ${setAliasResult.stdout}`);
  }

  async downloadCode(version = 'latest'): Promise<void> {
    await super.downloadCode(version);
  }

  static get exTester(): TestSetupAndRunner {
    if (TestSetupAndRunner.exTester) {
      return TestSetupAndRunner._exTester;
    }
    TestSetupAndRunner._exTester = new TestSetupAndRunner();
    return TestSetupAndRunner._exTester;
  }

  private async setupVirtualDisplay(): Promise<void> {
    if (process.platform !== 'linux') {
      return;
    }

    try {
      console.log('Setting up virtual display for Ubuntu...');

      // Start Xvfb virtual display
      await this.execShellCommand('Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &').catch(() => {
        console.log('Xvfb might already be running or not available');
      });

      // Wait for display to be ready
      await new Promise(resolve => setTimeout(resolve, 2000));

      // Verify display is working
      await this.execShellCommand('export DISPLAY=:99 && xdpyinfo > /dev/null 2>&1').catch(() => {
        console.log('Virtual display verification failed, continuing anyway');
      });

      console.log('Virtual display setup completed');
    } catch (error) {
      console.warn('Failed to setup virtual display:', error);
    }
  }

  private async execShellCommand(command: string): Promise<void> {
    const { exec } = await import('child_process');
    const { promisify } = await import('util');
    const execAsync = promisify(exec);
    await execAsync(command);
  }

  private async runTestsUbuntu(): Promise<number> {
    // Setup virtual display for Ubuntu
    await this.setupVirtualDisplay();

    console.log('Running tests...');

    // Kill any existing Chrome processes before running
    if (process.platform === 'linux') {
      await this.killChromeProcesses();
    }

    // Create a unique user data directory
    const uniqueUserDataDir = `/tmp/vscode-chrome-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;

    // Update environment variables with unique directory
    process.env.CHROME_USER_DATA_DIR = uniqueUserDataDir;

    // Call the existing runTests method
    const useExistingProject = EnvironmentSettings.getInstance().useExistingProject;
    const resources = useExistingProject ? [useExistingProject] : [];
    const exitCode = await super.runTests(this.spec || EnvironmentSettings.getInstance().specFiles, { resources });

    await this.validateTestExecution(exitCode);

    if (exitCode === 0) {
      console.log('Tests completed successfully!');
    }

    return exitCode;
  }

  /**
   * Validates that tests were actually executed by checking for test output
   * Throws an error if no tests were found or executed
   */
  private async validateTestExecution(exitCode: number): Promise<void> {
    if (exitCode !== 0) {
      return; // If tests failed, don't check for test count (tests were found but failed)
    }

    // Check if any spec files were provided
    const specFiles = this.spec || EnvironmentSettings.getInstance().specFiles;
    if (!specFiles || (Array.isArray(specFiles) && specFiles.length === 0)) {
      throw new Error('No E2E test spec files were provided. Please specify test files to run.');
    }

    // Check if spec files actually exist
    const specsToCheck = Array.isArray(specFiles) ? specFiles : [specFiles];
    let foundTestFiles = false;

    for (const spec of specsToCheck) {
      try {
        // Check if the spec file/pattern matches any files
        if (spec.includes('*') || spec.includes('?')) {
          // It's a glob pattern - we can't easily check without implementing glob matching
          // For now, assume it's valid if it contains wildcards
          log(`Spec pattern provided: ${spec}`);
          foundTestFiles = true;
        } else {
          // It's a specific file path
          await fs.access(spec);
          foundTestFiles = true;
          log(`Found test file: ${spec}`);
        }
      } catch (error) {
        log(`Test file not found: ${spec}`);
      }
    }

    if (!foundTestFiles && exitCode === 0) {
      throw new Error(
        `No E2E test files were found for the provided spec: ${JSON.stringify(specFiles)}. This likely indicates a configuration issue - tests should not pass when no test files exist.`
      );
    }

    log('Test execution validation completed - test files were found and executed');
  }

  private async killChromeProcesses(): Promise<void> {
    try {
      console.log('Killing existing Chrome processes...');

      const commands = [
        'pkill -f "chrome.*--test-type"',
        'pkill -f "chrome.*--remote-debugging-port"',
        'pkill -f "chrome.*--user-data-dir"',
        'pkill -f "chrome.*--disable-dev-shm-usage"',
        'pkill -f chromium',
        'pkill -f google-chrome',
        'pkill -f chrome',
        'pkill -f "Code.*--extensionDevelopmentPath"',
        'pkill -f "code.*--extensionDevelopmentPath"'
      ];

      for (const cmd of commands) {
        try {
          await this.execShellCommand(cmd);
        } catch (error) {
          // Ignore errors - processes might not exist
        }
      }

      // Clean up any leftover user data directories and lock files
      await this.execShellCommand(
        'rm -rf /tmp/chrome-user-data-* /tmp/vscode-chrome-* ~/.config/Code/SingletonLock ~/.vscode/extensions/*/node_modules/.bin/chromedriver'
      ).catch(() => {
        console.log('Failed to clean up leftover files, continuing anyway');
      });

      // Wait a moment for processes to fully terminate
      await new Promise(resolve => setTimeout(resolve, 3000));
    } catch (error) {
      console.warn('Failed to kill Chrome processes:', error);
    }
  }
}

// Parse command-line arguments
const argv = yargs(hideBin(process.argv))
  .option('spec', {
    alias: 's',
    type: 'string',
    description: 'Glob pattern for test files or list of test files',
    demandOption: false,
    array: true
  })
  .option('workspace-path', {
    alias: 'w',
    type: 'string',
    description: 'Path to workspace directory',
    demandOption: false
  })
  .option('vscode-version', {
    alias: 'v',
    type: 'string',
    description: 'VS Code version to use (e.g., latest, stable, 1.85.0)',
    demandOption: false
  })
  .help().argv as {
  spec: string | string[] | undefined;
  workspacePath?: string;
  vscodeVersion?: string;
};

// Create test config from command line arguments
const testConfig: Partial<TestConfig> = {};

if (argv.workspacePath) {
  testConfig.workspacePath = argv.workspacePath;
  testConfig.extensionsPath = path.join(argv.workspacePath, 'extensions');
}

if (argv.vscodeVersion) {
  testConfig.codeVersion = argv.vscodeVersion;
}

if (argv.spec) {
  log(`Spec passed in: ${argv.spec}`);
}

const testSetupAndRunner = new TestSetupAndRunner(testConfig, argv.spec);
async function run() {
  try {
    await testSetupAndRunner.setup();
    const result = await testSetupAndRunner.runTests();
    console.log(result);
    process.exit(result);
  } catch (error) {
    console.error(error);
    process.exit(1);
  }
}

void run();
