import {system, util} from '@appium/support';
import {retryInterval} from 'asyncbox';
import {exec} from 'teen_process';
import {CHROMEDRIVER_STATES} from '../constants';
import type {ChromedriverCommandContext} from './types';

const VERSION_PATTERN = /([\d.]+)/;

/**
 * Builds command-line arguments for the Chromedriver subprocess.
 */
export function buildChromedriverArgs(this: ChromedriverCommandContext): string[] {
  const args = [`--port=${this.proxyPort}`];
  if (this.adb?.adbPort) {
    args.push(`--adb-port=${this.adb.adbPort}`);
  }
  if (Array.isArray(this.cmdArgs)) {
    args.push(...this.cmdArgs);
  }
  if (this.logPath) {
    args.push(`--log-path=${this.logPath}`);
  }
  if (this.disableBuildCheck) {
    args.push('--disable-build-check');
  }
  args.push('--verbose');
  return args;
}

/**
 * Retrieves Chromedriver `/status` payload through the active proxy.
 */
export async function getStatus(this: ChromedriverCommandContext): Promise<any> {
  return await this.jwproxy.command('/status', 'GET');
}

/**
 * Polls Chromedriver until it reports ready and captures runtime metadata.
 */
export async function waitForOnline(this: ChromedriverCommandContext): Promise<void> {
  const self = this as ChromedriverCommandContext & {
    getStatus: () => Promise<any>;
  };
  let chromedriverStopped = false;
  await retryInterval(20, 200, async () => {
    if (this.state === CHROMEDRIVER_STATES.STOPPED) {
      chromedriverStopped = true;
      return;
    }
    const status = await self.getStatus();
    if (!util.isPlainObject(status) || !status.ready) {
      throw new Error(`The response to the /status API is not valid: ${JSON.stringify(status)}`);
    }
    const statusPayload = status as Record<string, any>;
    this._onlineStatus = statusPayload;
    const versionMatch = VERSION_PATTERN.exec(statusPayload.build?.version ?? '');
    if (versionMatch) {
      this._driverVersion = versionMatch[1];
      this.log.info(`Chromedriver version: ${this._driverVersion}`);
    } else {
      this.log.info('Chromedriver version cannot be determined from the /status API response');
    }
  });
  if (chromedriverStopped) {
    throw new Error('ChromeDriver crashed during startup.');
  }
}

/**
 * Cleans up stale Chromedriver processes and leftover adb forwarded ports.
 */
export async function killAll(this: ChromedriverCommandContext): Promise<void> {
  const cmd = system.isWindows() ? 'wmic' : 'pkill';
  const args = system.isWindows()
    ? [
        'process',
        'where',
        `commandline like '%chromedriver.exe%--port=${this.proxyPort}%'`,
        'delete',
      ]
    : ['-15', '-f', `${this.chromedriver}.*--port=${this.proxyPort}`];
  this.log.debug(`Killing any old chromedrivers, running: ${cmd} ${args.join(' ')}`);
  try {
    await exec(cmd, args);
    this.log.debug('Successfully cleaned up old chromedrivers');
  } catch {
    this.log.info('No old chromedrivers seem to exist');
  }

  if (this.adb) {
    const udidIndex = this.adb.executable.defaultArgs.findIndex((item: string) => item === '-s');
    const udid = udidIndex > -1 ? this.adb.executable.defaultArgs[udidIndex + 1] : null;
    if (udid) {
      this.log.debug(`Cleaning this device's adb forwarded port socket connections: ${udid}`);
    } else {
      this.log.debug(`Cleaning any old adb forwarded port socket connections`);
    }

    try {
      for (const conn of await this.adb.getForwardList()) {
        if (!(conn.includes('webview_devtools') && (!udid || conn.includes(udid)))) {
          continue;
        }
        const params = conn.split(/\s+/);
        if (params.length > 1) {
          await this.adb.removePortForward(params[1].replace(/[\D]*/, ''));
        }
      }
    } catch (e) {
      const err = e as Error;
      this.log.warn(`Unable to clean forwarded ports. Error: '${err.message}'. Continuing.`);
    }
  }
}
