import { Page, Request, Response } from '@playwright/test';

export interface APICall {
  method: string;
  url: string;
  pathname: string;
  timestamp: number;
  requestHeaders?: Record<string, string>;
  requestBody?: any;
  responseStatus?: number;
  responseHeaders?: Record<string, string>;
  responseBody?: any;
  duration?: number;
}

export class APIInterceptor {
  private calls: APICall[] = [];
  private pendingRequests: Map<string, APICall> = new Map();

  /**
   * Start intercepting API calls on the given page
   */
  intercept(page: Page) {
    page.on('request', (request: Request) => {
      this.recordRequest(request);
    });

    page.on('response', async (response: Response) => {
      await this.recordResponse(response);
    });
  }

  /**
   * Record an API request
   */
  private recordRequest(request: Request) {
    const url = request.url();

    // Only capture API calls
    if (!url.includes('/api/')) {
      return;
    }

    const parsedUrl = new URL(url);
    const call: APICall = {
      method: request.method(),
      url: url,
      pathname: parsedUrl.pathname,
      timestamp: Date.now(),
      requestHeaders: request.headers(),
    };

    // Try to capture request body
    try {
      const postData = request.postData();
      if (postData) {
        try {
          call.requestBody = JSON.parse(postData);
        } catch {
          call.requestBody = postData;
        }
      }
    } catch {
      // Ignore if we can't get post data
    }

    // Store as pending until we get the response
    this.pendingRequests.set(url, call);
  }

  /**
   * Record an API response and merge with request data
   */
  private async recordResponse(response: Response) {
    const url = response.url();

    // Only capture API calls
    if (!url.includes('/api/')) {
      return;
    }

    const pendingCall = this.pendingRequests.get(url);

    if (pendingCall) {
      // Calculate duration
      pendingCall.duration = Date.now() - pendingCall.timestamp;
      pendingCall.responseStatus = response.status();
      pendingCall.responseHeaders = response.headers();

      // Try to capture response body
      try {
        const contentType = response.headers()['content-type'] || '';
        if (contentType.includes('application/json')) {
          pendingCall.responseBody = await response.json();
        }
      } catch {
        // Ignore if we can't parse response
      }

      this.calls.push(pendingCall);
      this.pendingRequests.delete(url);
    } else {
      // Response without matching request - create minimal record
      const parsedUrl = new URL(url);
      this.calls.push({
        method: 'UNKNOWN',
        url: url,
        pathname: parsedUrl.pathname,
        timestamp: Date.now(),
        responseStatus: response.status(),
        responseHeaders: response.headers(),
      });
    }
  }

  /**
   * Get all captured API calls
   */
  getCalls(): APICall[] {
    return this.calls;
  }

  /**
   * Get calls filtered by method
   */
  getCallsByMethod(method: string): APICall[] {
    return this.calls.filter(call => call.method === method);
  }

  /**
   * Get calls filtered by pathname pattern
   */
  getCallsByPath(pattern: string | RegExp): APICall[] {
    if (typeof pattern === 'string') {
      return this.calls.filter(call => call.pathname.includes(pattern));
    } else {
      return this.calls.filter(call => pattern.test(call.pathname));
    }
  }

  /**
   * Get unique endpoints called
   */
  getUniqueEndpoints(): string[] {
    const endpoints = new Set<string>();
    for (const call of this.calls) {
      endpoints.add(`${call.method} ${call.pathname}`);
    }
    return Array.from(endpoints);
  }

  /**
   * Reset the interceptor
   */
  reset() {
    this.calls = [];
    this.pendingRequests.clear();
  }

  /**
   * Export calls to JSON
   */
  toJSON(): string {
    return JSON.stringify(this.calls, null, 2);
  }
}
