import { spawn, ChildProcess } from 'child_process';
import { join } from 'path';

export interface WebsiteServerHarnessOptions {
  port?: number;
  env?: Record<string, string>;
  cwd?: string;
}

export class WebsiteServerHarness {
  private process: ChildProcess | null = null;
  private logs: string[] = [];
  private port: number;
  private options: WebsiteServerHarnessOptions;

  constructor(options: WebsiteServerHarnessOptions = {}) {
    this.options = options;
    this.port = options.port || 3000;
  }

  async start(): Promise<void> {
    return new Promise((resolve, reject) => {
      const cwd = join(process.cwd(), 'apps/website');
      
      // Use 'npm run dev' or 'npm run start' depending on environment
      // For integration tests, 'dev' is often easier if we don't want to build first,
      // but 'start' is more realistic for SSR.
      // Assuming 'npm run dev' for now as it's faster for local tests.
      this.process = spawn('npm', ['run', 'dev', '--', '-p', this.port.toString()], {
        cwd,
        env: {
          ...process.env,
          ...this.options.env,
          PORT: this.port.toString(),
        },
        shell: true,
        detached: true, // Start in a new process group
      });

      let resolved = false;

      const checkReadiness = async () => {
        if (resolved) return;
        try {
          const res = await fetch(`http://localhost:${this.port}`, { method: 'HEAD' });
          if (res.ok || res.status === 307 || res.status === 200) {
            resolved = true;
            resolve();
          }
        } catch (e) {
          // Not ready yet
        }
      };

      this.process.stdout?.on('data', (data) => {
        const str = data.toString();
        this.logs.push(str);
        if (str.includes('ready') || str.includes('started') || str.includes('Local:')) {
          checkReadiness();
        }
      });

      this.process.stderr?.on('data', (data) => {
        const str = data.toString();
        this.logs.push(str);
        // Don't console.error here as it might be noisy, but keep in logs
      });

      this.process.on('error', (err) => {
        if (!resolved) {
          resolved = true;
          reject(err);
        }
      });

      this.process.on('exit', (code) => {
        if (!resolved && code !== 0 && code !== null) {
          resolved = true;
          reject(new Error(`Website server exited with code ${code}`));
        }
      });

      // Timeout after 60 seconds (Next.js dev can be slow)
      setTimeout(() => {
        if (!resolved) {
          resolved = true;
          reject(new Error(`Website server failed to start within 60s. Logs:\n${this.getLogTail(20)}`));
        }
      }, 60000);
    });
  }

  async stop(): Promise<void> {
    if (this.process && this.process.pid) {
      try {
        // Kill the process group since we used detached: true
        process.kill(-this.process.pid);
      } catch (e) {
        // Fallback to normal kill
        this.process.kill();
      }
      this.process = null;
    }
  }

  getLogs(): string[] {
    return this.logs;
  }

  getLogTail(lines: number = 60): string {
    return this.logs.slice(-lines).join('');
  }

  hasErrorPatterns(): boolean {
    const errorPatterns = [
      'uncaughtException',
      'unhandledRejection',
      // 'Error: ', // Too broad, catches expected API errors
    ];
    
    // Only fail on actual process-level errors or unexpected server crashes
    return this.logs.some(log => errorPatterns.some(pattern => log.includes(pattern)));
  }
}
