import { APICall } from './api-interceptor';

export interface ComparisonResult {
  matches: boolean;
  totalCalls: number;
  matchedCalls: number;
  missingCalls: EndpointMismatch[];
  extraCalls: EndpointMismatch[];
  outOfOrderCalls: OrderMismatch[];
  timingVariances: TimingVariance[];
  report: string;
}

export interface EndpointMismatch {
  endpoint: string;
  expectedCount: number;
  actualCount: number;
}

export interface OrderMismatch {
  endpoint: string;
  expectedPosition: number;
  actualPosition: number;
  deviation: number;
}

export interface TimingVariance {
  endpoint: string;
  expectedTiming: number;
  actualTiming: number;
  variance: number;
}

/**
 * Compare two API call sequences
 */
export function compareAPISequences(
  actual: APICall[],
  expected: APICall[]
): ComparisonResult {
  const missing: EndpointMismatch[] = [];
  const extra: EndpointMismatch[] = [];
  const outOfOrder: OrderMismatch[] = [];
  const timingVariances: TimingVariance[] = [];

  // Build endpoint maps
  const actualEndpoints = buildEndpointMap(actual);
  const expectedEndpoints = buildEndpointMap(expected);

  // Find missing calls (in expected but not in actual)
  for (const [endpoint, expectedCalls] of expectedEndpoints.entries()) {
    const actualCalls = actualEndpoints.get(endpoint) || [];
    if (actualCalls.length === 0) {
      missing.push({
        endpoint,
        expectedCount: expectedCalls.length,
        actualCount: 0,
      });
    } else if (actualCalls.length < expectedCalls.length) {
      missing.push({
        endpoint,
        expectedCount: expectedCalls.length,
        actualCount: actualCalls.length,
      });
    }
  }

  // Find extra calls (in actual but not in expected)
  for (const [endpoint, actualCalls] of actualEndpoints.entries()) {
    const expectedCalls = expectedEndpoints.get(endpoint) || [];
    if (expectedCalls.length === 0) {
      extra.push({
        endpoint,
        expectedCount: 0,
        actualCount: actualCalls.length,
      });
    } else if (actualCalls.length > expectedCalls.length) {
      extra.push({
        endpoint,
        expectedCount: expectedCalls.length,
        actualCount: actualCalls.length,
      });
    }
  }

  // Check order
  const orderMismatches = checkOrder(actual, expected);
  outOfOrder.push(...orderMismatches);

  // Check timing
  const timings = checkTimingVariance(actual, expected);
  timingVariances.push(...timings);

  // Calculate match percentage
  const totalExpected = expected.length;
  const matched = totalExpected - missing.reduce((sum, m) => sum + (m.expectedCount - m.actualCount), 0);
  const matchPercentage = totalExpected > 0 ? (matched / totalExpected) * 100 : 0;

  // Generate report
  const report = generateReport({
    matches: matchPercentage >= 95,
    totalCalls: actual.length,
    matchedCalls: matched,
    missingCalls: missing,
    extraCalls: extra,
    outOfOrderCalls: outOfOrder,
    timingVariances: timingVariances,
    report: '',
  });

  return {
    matches: matchPercentage >= 95,
    totalCalls: actual.length,
    matchedCalls: matched,
    missingCalls: missing,
    extraCalls: extra,
    outOfOrderCalls: outOfOrder,
    timingVariances: timingVariances,
    report,
  };
}

/**
 * Build a map of endpoint -> calls
 */
function buildEndpointMap(calls: APICall[]): Map<string, APICall[]> {
  const map = new Map<string, APICall[]>();
  for (const call of calls) {
    const endpoint = `${call.method} ${call.pathname}`;
    if (!map.has(endpoint)) {
      map.set(endpoint, []);
    }
    map.get(endpoint)!.push(call);
  }
  return map;
}

/**
 * Check if calls are in the same order
 */
function checkOrder(actual: APICall[], expected: APICall[]): OrderMismatch[] {
  const mismatches: OrderMismatch[] = [];

  // Create a sequence of endpoints for both
  const actualSequence = actual.map(c => `${c.method} ${c.pathname}`);
  const expectedSequence = expected.map(c => `${c.method} ${c.pathname}`);

  // For each expected endpoint, find its position in actual
  for (let i = 0; i < expectedSequence.length; i++) {
    const endpoint = expectedSequence[i];
    const actualIndex = actualSequence.indexOf(endpoint);

    if (actualIndex !== -1) {
      const deviation = Math.abs(actualIndex - i);
      // Only report if deviation is significant (more than 3 positions)
      if (deviation > 3) {
        mismatches.push({
          endpoint,
          expectedPosition: i,
          actualPosition: actualIndex,
          deviation,
        });
      }
    }
  }

  return mismatches;
}

/**
 * Check timing variance between calls
 */
function checkTimingVariance(actual: APICall[], expected: APICall[]): TimingVariance[] {
  const variances: TimingVariance[] = [];

  if (actual.length === 0 || expected.length === 0) {
    return variances;
  }

  // Calculate relative timings (time from first call)
  const actualStartTime = actual[0].timestamp;
  const expectedStartTime = expected[0].timestamp;

  const actualTimings = new Map<string, number[]>();
  const expectedTimings = new Map<string, number[]>();

  for (const call of actual) {
    const endpoint = `${call.method} ${call.pathname}`;
    const relativeTime = call.timestamp - actualStartTime;
    if (!actualTimings.has(endpoint)) {
      actualTimings.set(endpoint, []);
    }
    actualTimings.get(endpoint)!.push(relativeTime);
  }

  for (const call of expected) {
    const endpoint = `${call.method} ${call.pathname}`;
    const relativeTime = call.timestamp - expectedStartTime;
    if (!expectedTimings.has(endpoint)) {
      expectedTimings.set(endpoint, []);
    }
    expectedTimings.get(endpoint)!.push(relativeTime);
  }

  // Compare average timings
  for (const [endpoint, expectedTimes] of expectedTimings.entries()) {
    const actualTimes = actualTimings.get(endpoint);
    if (actualTimes && actualTimes.length > 0) {
      const avgExpected = expectedTimes.reduce((a, b) => a + b, 0) / expectedTimes.length;
      const avgActual = actualTimes.reduce((a, b) => a + b, 0) / actualTimes.length;
      const variance = Math.abs(avgActual - avgExpected);

      // Only report if variance is significant (more than 500ms)
      if (variance > 500) {
        variances.push({
          endpoint,
          expectedTiming: avgExpected,
          actualTiming: avgActual,
          variance,
        });
      }
    }
  }

  return variances;
}

/**
 * Generate a human-readable comparison report
 */
function generateReport(result: ComparisonResult): string {
  let report = '# API Sequence Comparison Report\n\n';

  report += `## Summary\n\n`;
  report += `- **Match Status:** ${result.matches ? '✅ PASS' : '❌ FAIL'}\n`;
  report += `- **Total Calls:** ${result.totalCalls}\n`;
  report += `- **Matched Calls:** ${result.matchedCalls}\n`;
  report += `- **Match Percentage:** ${((result.matchedCalls / result.totalCalls) * 100).toFixed(1)}%\n\n`;

  if (result.missingCalls.length > 0) {
    report += `## ❌ Missing API Calls\n\n`;
    report += `The following expected API calls are missing or called fewer times:\n\n`;
    for (const missing of result.missingCalls) {
      report += `- **${missing.endpoint}**\n`;
      report += `  - Expected: ${missing.expectedCount} call(s)\n`;
      report += `  - Actual: ${missing.actualCount} call(s)\n`;
      report += `  - Missing: ${missing.expectedCount - missing.actualCount} call(s)\n\n`;
    }
  }

  if (result.extraCalls.length > 0) {
    report += `## ⚠️ Extra API Calls\n\n`;
    report += `The following API calls were made but not expected:\n\n`;
    for (const extra of result.extraCalls) {
      report += `- **${extra.endpoint}**\n`;
      report += `  - Expected: ${extra.expectedCount} call(s)\n`;
      report += `  - Actual: ${extra.actualCount} call(s)\n`;
      report += `  - Extra: ${extra.actualCount - extra.expectedCount} call(s)\n\n`;
    }
  }

  if (result.outOfOrderCalls.length > 0) {
    report += `## ⚠️ Out of Order Calls\n\n`;
    report += `The following API calls occurred in a different order:\n\n`;
    for (const order of result.outOfOrderCalls) {
      report += `- **${order.endpoint}**\n`;
      report += `  - Expected position: ${order.expectedPosition}\n`;
      report += `  - Actual position: ${order.actualPosition}\n`;
      report += `  - Deviation: ${order.deviation} positions\n\n`;
    }
  }

  if (result.timingVariances.length > 0) {
    report += `## ⏱️ Timing Variances\n\n`;
    report += `The following API calls have significant timing differences:\n\n`;
    for (const timing of result.timingVariances) {
      report += `- **${timing.endpoint}**\n`;
      report += `  - Expected timing: ${timing.expectedTiming.toFixed(0)}ms\n`;
      report += `  - Actual timing: ${timing.actualTiming.toFixed(0)}ms\n`;
      report += `  - Variance: ${timing.variance.toFixed(0)}ms\n\n`;
    }
  }

  if (result.matches) {
    report += `## ✅ Conclusion\n\n`;
    report += `The API sequence matches the expected baseline with acceptable variance.\n`;
  } else {
    report += `## ❌ Conclusion\n\n`;
    report += `The API sequence does NOT match the expected baseline. Please review the mismatches above.\n`;
  }

  return report;
}
