/**
 * API Smoke Test
 * 
 * This test performs true e2e testing of all API endpoints by making direct HTTP requests
 * to the running API server. It tests for:
 * - Basic connectivity and response codes
 * - Presenter errors ("Presenter not presented")
 * - Response format validation
 * - Error handling
 * 
 * This test is designed to run in the Docker e2e environment and can be executed with:
 * npm run test:e2e:website (which runs everything in Docker)
 */

import { test, expect, request } from '@playwright/test';
import * as fs from 'fs/promises';
import * as path from 'path';

interface EndpointTestResult {
  endpoint: string;
  method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
  status: number;
  success: boolean;
  error?: string;
  response?: unknown;
  hasPresenterError: boolean;
  responseTime: number;
}

interface TestReport {
  timestamp: string;
  summary: {
    total: number;
    success: number;
    failed: number;
    presenterErrors: number;
    avgResponseTime: number;
  };
  results: EndpointTestResult[];
  failures: EndpointTestResult[];
}

const API_BASE_URL = process.env.API_BASE_URL ?? process.env.NEXT_PUBLIC_API_BASE_URL ?? 'http://localhost:3101';

// Auth file paths
const USER_AUTH_FILE = path.join(__dirname, '.auth/user-session.json');
const ADMIN_AUTH_FILE = path.join(__dirname, '.auth/admin-session.json');

test.describe('API Smoke Tests', () => {
  // Aggregate across the whole suite (used for final report).
  const allResults: EndpointTestResult[] = [];

  let testResults: EndpointTestResult[] = [];

  test.beforeAll(async () => {
    console.log(`[API SMOKE] Testing API at: ${API_BASE_URL}`);
    
    // Verify auth files exist
    const userAuthExists = await fs.access(USER_AUTH_FILE).then(() => true).catch(() => false);
    const adminAuthExists = await fs.access(ADMIN_AUTH_FILE).then(() => true).catch(() => false);
    
    if (!userAuthExists || !adminAuthExists) {
      throw new Error('Auth files not found. Run global setup first.');
    }
    
    console.log('[API SMOKE] Auth files verified');
  });

  test.afterAll(async () => {
    await generateReport();
  });

  test('all public GET endpoints respond correctly', async ({ request }) => {
    testResults = [];
    const endpoints = [
      // Race endpoints
      { method: 'GET' as const, path: '/races/all', name: 'Get all races' },
      { method: 'GET' as const, path: '/races/total-races', name: 'Get total races count' },
      { method: 'GET' as const, path: '/races/page-data', name: 'Get races page data' },
      { method: 'GET' as const, path: '/races/all/page-data', name: 'Get all races page data' },
      { method: 'GET' as const, path: '/races/reference/penalty-types', name: 'Get penalty types reference' },
      
      // League endpoints
      { method: 'GET' as const, path: '/leagues/all-with-capacity', name: 'Get all leagues' },
      { method: 'GET' as const, path: '/leagues/available', name: 'Get available leagues' },
      
      // Team endpoints
      { method: 'GET' as const, path: '/teams/all', name: 'Get all teams' },
      
      // Driver endpoints
      { method: 'GET' as const, path: '/drivers/leaderboard', name: 'Get driver leaderboard' },
      { method: 'GET' as const, path: '/drivers/total-drivers', name: 'Get total drivers count' },
      
      // Sponsor endpoints
      { method: 'GET' as const, path: '/sponsors/pricing', name: 'Get sponsorship pricing' },
      
      // Features endpoint
      { method: 'GET' as const, path: '/features', name: 'Get feature flags' },
      
      // Hello endpoint
      { method: 'GET' as const, path: '/hello', name: 'Hello World' },
      
      // Media endpoints
      { method: 'GET' as const, path: '/media/avatar/non-existent-id', name: 'Get non-existent avatar' },
      
      // Driver by ID
      { method: 'GET' as const, path: '/drivers/non-existent-id', name: 'Get non-existent driver' },
    ];

    console.log(`\n[API SMOKE] Testing ${endpoints.length} public GET endpoints...`);

    for (const endpoint of endpoints) {
      await testEndpoint(request, endpoint);
    }

    // Check for failures
    const failures = testResults.filter(r => !r.success);
    if (failures.length > 0) {
      console.log('\n❌ FAILURES FOUND:');
      failures.forEach(r => {
        console.log(`  ${r.method} ${r.endpoint} - ${r.status} - ${r.error || r.response}`);
      });
    }

    // Assert all endpoints succeeded
    expect(failures.length).toBe(0);
  });

  test('POST endpoints handle requests gracefully', async ({ request }) => {
    testResults = [];
    const endpoints = [
      // Auth endpoints (no auth required)
      { method: 'POST' as const, path: '/auth/signup', name: 'Signup', requiresAuth: false, body: { email: `test-smoke-${Date.now()}@example.com`, password: 'Password123', displayName: 'Smoke Test', username: 'smoketest' } },
      { method: 'POST' as const, path: '/auth/login', name: 'Login', requiresAuth: false, body: { email: 'demo.driver@example.com', password: 'Demo1234!' } },
      
      // Protected endpoints (require auth)
      { method: 'POST' as const, path: '/races/123/register', name: 'Register for race', requiresAuth: true, body: { driverId: 'test-driver' } },
      { method: 'POST' as const, path: '/races/protests/file', name: 'File protest', requiresAuth: true, body: { raceId: '123', protestingDriverId: 'driver-1', accusedDriverId: 'driver-2', incident: { lap: 1, description: 'Test protest' } } },
      { method: 'POST' as const, path: '/leagues/league-1/join', name: 'Join league', requiresAuth: true, body: {} },
      { method: 'POST' as const, path: '/teams/123/join', name: 'Join team', requiresAuth: true, body: { teamId: '123' } },
    ];

    console.log(`\n[API SMOKE] Testing ${endpoints.length} POST endpoints...`);

    for (const endpoint of endpoints) {
      await testEndpoint(request, endpoint);
    }

    // Check for presenter errors
    const presenterErrors = testResults.filter(r => r.hasPresenterError);
    expect(presenterErrors.length).toBe(0);
  });

  test('parameterized endpoints handle missing IDs gracefully', async ({ request }) => {
    testResults = [];
    const endpoints = [
      { method: 'GET' as const, path: '/races/non-existent-id', name: 'Get non-existent race', requiresAuth: false },
      { method: 'GET' as const, path: '/races/non-existent-id/results', name: 'Get non-existent race results', requiresAuth: false },
      { method: 'GET' as const, path: '/leagues/non-existent-id', name: 'Get non-existent league', requiresAuth: false },
      { method: 'GET' as const, path: '/teams/non-existent-id', name: 'Get non-existent team', requiresAuth: false },
      { method: 'GET' as const, path: '/drivers/non-existent-id', name: 'Get non-existent driver', requiresAuth: false },
      { method: 'GET' as const, path: '/media/avatar/non-existent-id', name: 'Get non-existent avatar', requiresAuth: false },
    ];

    console.log(`\n[API SMOKE] Testing ${endpoints.length} parameterized endpoints with invalid IDs...`);

    for (const endpoint of endpoints) {
      await testEndpoint(request, endpoint);
    }

    // Check for failures
    const failures = testResults.filter(r => !r.success);
    expect(failures.length).toBe(0);
  });

  test('authenticated endpoints respond correctly', async () => {
    testResults = [];
    
    // Load user auth cookies
    const userAuthData = await fs.readFile(USER_AUTH_FILE, 'utf-8');
    const userCookies = JSON.parse(userAuthData).cookies;
    
    // Create new API request context with user auth
    const userContext = await request.newContext({ 
      storageState: { 
        cookies: userCookies,
        origins: [{ origin: API_BASE_URL, localStorage: [] }]
      } 
    });
    
    const endpoints = [
      // Dashboard
      { method: 'GET' as const, path: '/dashboard/overview', name: 'Dashboard Overview' },
      
      // Analytics
      { method: 'GET' as const, path: '/analytics/metrics', name: 'Analytics Metrics' },
      
      // Notifications
      { method: 'GET' as const, path: '/notifications/unread', name: 'Unread Notifications' },
    ];

    console.log(`\n[API SMOKE] Testing ${endpoints.length} authenticated endpoints...`);

    for (const endpoint of endpoints) {
      await testEndpoint(userContext, endpoint);
    }

    // Check for presenter errors
    const presenterErrors = testResults.filter(r => r.hasPresenterError);
    expect(presenterErrors.length).toBe(0);
    
    // Clean up
    await userContext.dispose();
  });

  test('admin endpoints respond correctly', async () => {
    testResults = [];
    
    // Load admin auth cookies
    const adminAuthData = await fs.readFile(ADMIN_AUTH_FILE, 'utf-8');
    const adminCookies = JSON.parse(adminAuthData).cookies;
    
    // Create new API request context with admin auth
    const adminContext = await request.newContext({ 
      storageState: { 
        cookies: adminCookies,
        origins: [{ origin: API_BASE_URL, localStorage: [] }]
      } 
    });
    
    const endpoints = [
      // Payments (requires admin capability)
      { method: 'GET' as const, path: '/payments/wallets?leagueId=league-1', name: 'Wallets' },
    ];

    console.log(`\n[API SMOKE] Testing ${endpoints.length} admin endpoints...`);

    for (const endpoint of endpoints) {
      await testEndpoint(adminContext, endpoint);
    }

    // Check for presenter errors
    const presenterErrors = testResults.filter(r => r.hasPresenterError);
    expect(presenterErrors.length).toBe(0);
    
    // Clean up
    await adminContext.dispose();
  });

  async function testEndpoint(
    request: import('@playwright/test').APIRequestContext,
    endpoint: { method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; path: string; name?: string; body?: unknown; requiresAuth?: boolean }
  ): Promise<void> {
    const startTime = Date.now();
    const fullUrl = `${API_BASE_URL}${endpoint.path}`;
    
    console.log(`\n[TEST] ${endpoint.method} ${endpoint.path} (${endpoint.name || 'Unknown'})`);

    try {
      let response;
      const headers: Record<string, string> = {};
      
      // Playwright's request context handles cookies automatically
      // No need to set Authorization header for cookie-based auth
      
      switch (endpoint.method) {
        case 'GET':
          response = await request.get(fullUrl, { headers });
          break;
        case 'POST':
          response = await request.post(fullUrl, { data: endpoint.body || {}, headers });
          break;
        case 'PUT':
          response = await request.put(fullUrl, { data: endpoint.body || {}, headers });
          break;
        case 'DELETE':
          response = await request.delete(fullUrl, { headers });
          break;
        case 'PATCH':
          response = await request.patch(fullUrl, { data: endpoint.body || {}, headers });
          break;
      }

      const responseTime = Date.now() - startTime;
      const status = response.status();
      const body = await response.json().catch(() => null);
      const bodyText = await response.text().catch(() => '');

      // Check for presenter errors
      const hasPresenterError =
        bodyText.includes('Presenter not presented') ||
        bodyText.includes('presenter not presented') ||
        (body && body.message && body.message.includes('Presenter not presented')) ||
        (body && body.error && body.error.includes('Presenter not presented'));

      // Success is 200-299 status, or 404 for non-existent resources, and no presenter error
      const isNotFound = status === 404;
      const success = (status >= 200 && status < 300 || isNotFound) && !hasPresenterError;

      const result: EndpointTestResult = {
        endpoint: endpoint.path,
        method: endpoint.method,
        status,
        success,
        hasPresenterError,
        responseTime,
        response: body || bodyText.substring(0, 200),
      };

      if (!success) {
        result.error = body?.message || bodyText.substring(0, 200);
      }

      testResults.push(result);
      allResults.push(result);

      if (hasPresenterError) {
        console.log(`  ❌ PRESENTER ERROR: ${status} - ${body?.message || bodyText.substring(0, 100)}`);
      } else if (success) {
        console.log(`  ✅ ${status} (${responseTime}ms)`);
      } else {
        console.log(`  ⚠️  ${status} (${responseTime}ms) - ${body?.message || 'Error'}`);
      }

    } catch (error: unknown) {
      const responseTime = Date.now() - startTime;
      const errorString = error instanceof Error ? error.message : String(error);
      
      const result: EndpointTestResult = {
        endpoint: endpoint.path,
        method: endpoint.method,
        status: 0,
        success: false,
        hasPresenterError: false,
        responseTime,
        error: errorString,
      };

      // Check if it's a presenter error
      if (errorString.includes('Presenter not presented')) {
        result.hasPresenterError = true;
        console.log(`  ❌ PRESENTER ERROR (exception): ${errorString}`);
      } else {
        console.log(`  ❌ EXCEPTION: ${errorString}`);
      }

      testResults.push(result);
      allResults.push(result);
    }
  }

  async function generateReport(): Promise<void> {
    const summary = {
      total: allResults.length,
      success: allResults.filter(r => r.success).length,
      failed: allResults.filter(r => !r.success).length,
      presenterErrors: allResults.filter(r => r.hasPresenterError).length,
      avgResponseTime: allResults.reduce((sum, r) => sum + r.responseTime, 0) / allResults.length || 0,
    };

    const report: TestReport = {
      timestamp: new Date().toISOString(),
      summary,
      results: allResults,
      failures: allResults.filter(r => !r.success),
    };

    // Write JSON report
    const jsonPath = path.join(__dirname, '../../../api-smoke-report.json');
    await fs.writeFile(jsonPath, JSON.stringify(report, null, 2));
    
    // Write Markdown report
    const mdPath = path.join(__dirname, '../../../api-smoke-report.md');
    let md = `# API Smoke Test Report\n\n`;
    md += `**Generated:** ${new Date().toISOString()}\n`;
    md += `**API Base URL:** ${API_BASE_URL}\n\n`;
    
    md += `## Summary\n\n`;
    md += `- **Total Endpoints:** ${summary.total}\n`;
    md += `- **✅ Success:** ${summary.success}\n`;
    md += `- **❌ Failed:** ${summary.failed}\n`;
    md += `- **⚠️  Presenter Errors:** ${summary.presenterErrors}\n`;
    md += `- **Avg Response Time:** ${summary.avgResponseTime.toFixed(2)}ms\n\n`;

    if (summary.presenterErrors > 0) {
      md += `## Presenter Errors\n\n`;
      const presenterFailures = allResults.filter(r => r.hasPresenterError);
      presenterFailures.forEach((r, i) => {
        md += `${i + 1}. **${r.method} ${r.endpoint}**\n`;
        md += `   - Status: ${r.status}\n`;
        md += `   - Error: ${r.error || 'No error message'}\n\n`;
      });
    }

    if (summary.failed > 0 && summary.presenterErrors < summary.failed) {
      md += `## Other Failures\n\n`;
      const otherFailures = allResults.filter(r => !r.success && !r.hasPresenterError);
      otherFailures.forEach((r, i) => {
        md += `${i + 1}. **${r.method} ${r.endpoint}**\n`;
        md += `   - Status: ${r.status}\n`;
        md += `   - Error: ${r.error || 'No error message'}\n\n`;
      });
    }

    await fs.writeFile(mdPath, md);

    console.log(`\n📊 Reports generated:`);
    console.log(`  JSON: ${jsonPath}`);
    console.log(`  Markdown: ${mdPath}`);
    console.log(`\nSummary: ${summary.success}/${summary.total} passed, ${summary.presenterErrors} presenter errors`);
  }
});