/**
 * Electrobun stack adapter — spawns WebKitWebDriver or electrobun binary,
 * manages PTY daemon lifecycle for terminal tests.
 */

import { spawn, type ChildProcess } from 'node:child_process';
import { createConnection } from 'node:net';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { existsSync } from 'node:fs';
import { StackAdapter, type StackCapabilities } from './base-adapter.ts';
import type { TestFixture } from '../infra/fixtures.ts';

const __dirname = dirname(fileURLToPath(import.meta.url));
const PROJECT_ROOT = resolve(__dirname, '../../..');
const ELECTROBUN_ROOT = resolve(PROJECT_ROOT, 'ui-electrobun');

export class ElectrobunAdapter extends StackAdapter {
  readonly name = 'electrobun';
  readonly port = 9761;

  getBinaryPath(): string {
    return resolve(ELECTROBUN_ROOT, 'build/Agent Orchestrator');
  }

  getDataDir(fixture: TestFixture): string {
    return fixture.dataDir;
  }

  getCapabilities(_fixture: TestFixture): StackCapabilities {
    return {
      capabilities: {
        'wdio:enforceWebDriverClassic': true,
        browserName: 'webkit',
      },
    };
  }

  setupDriver(): Promise<ChildProcess> {
    return new Promise((res, reject) => {
      // Check port is free
      const preCheck = createConnection({ port: this.port, host: 'localhost' }, () => {
        preCheck.destroy();
        reject(new Error(
          `Port ${this.port} already in use. Kill: lsof -ti:${this.port} | xargs kill`
        ));
      });

      preCheck.on('error', () => {
        preCheck.destroy();

        // Try WebKitWebDriver first (system-installed), fall back to electrobun binary
        const driverBin = this.findWebDriver();
        const driver = spawn(driverBin, ['--port', String(this.port)], {
          stdio: ['ignore', 'pipe', 'pipe'],
          env: {
            ...process.env,
            AGOR_TEST: '1',
          },
        });

        driver.on('error', (err) => {
          reject(new Error(
            `Failed to start WebDriver for Electrobun: ${err.message}. ` +
            'Ensure WebKitWebDriver or the Electrobun binary is available.'
          ));
        });

        // TCP readiness probe
        const deadline = Date.now() + 15_000;
        const probe = () => {
          if (Date.now() > deadline) {
            reject(new Error(`WebDriver not ready on port ${this.port} within 15s`));
            return;
          }
          const sock = createConnection({ port: this.port, host: 'localhost' }, () => {
            sock.destroy();
            res(driver);
          });
          sock.on('error', () => {
            sock.destroy();
            setTimeout(probe, 300);
          });
        };
        setTimeout(probe, 500);
      });
    });
  }

  teardownDriver(driver: ChildProcess): void {
    driver.kill();
  }

  async startPtyDaemon(): Promise<ChildProcess> {
    const daemonPath = resolve(ELECTROBUN_ROOT, 'src/pty-daemon/agor-ptyd');
    const altPath = resolve(PROJECT_ROOT, 'target/debug/agor-ptyd');
    const bin = existsSync(daemonPath) ? daemonPath : altPath;

    if (!existsSync(bin)) {
      throw new Error(`PTY daemon binary not found at ${daemonPath} or ${altPath}`);
    }

    const daemon = spawn(bin, [], {
      stdio: ['ignore', 'pipe', 'pipe'],
      env: { ...process.env, AGOR_TEST: '1' },
    });

    // Wait for daemon to be ready (simple delay — daemon binds quickly)
    await new Promise((r) => setTimeout(r, 1000));
    return daemon;
  }

  stopPtyDaemon(daemon: ChildProcess): void {
    daemon.kill('SIGTERM');
  }

  verifyBinary(): void {
    if (!existsSync(this.getBinaryPath())) {
      throw new Error(
        `Electrobun binary not found at ${this.getBinaryPath()}. ` +
        'Build with: cd ui-electrobun && bun run build:canary'
      );
    }
  }

  private findWebDriver(): string {
    // Check common WebKitWebDriver locations
    const candidates = [
      '/usr/bin/WebKitWebDriver',
      '/usr/local/bin/WebKitWebDriver',
      resolve(ELECTROBUN_ROOT, 'node_modules/.bin/webkitwebdriver'),
    ];
    for (const c of candidates) {
      if (existsSync(c)) return c;
    }
    // Fall back to PATH resolution
    return 'WebKitWebDriver';
  }
}
