/**
 * E2E-10: Multi-Profile Error Handling
 * Validates: error cases for multi-profile feature including invalid profiles,
 * pool capacity limits, crash isolation, and backward compatibility.
 */
import * as fs from 'fs';
import * as path from 'path';
import { MCPClient, MCPToolResult } from '../harness/mcp-client';
import { scaledSleep } from '../harness/time-scale';

function getFixturePort(): number {
  const stateFile = path.join(process.cwd(), '.e2e-state.json');
  const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
  return state.port;
}

/** Parse JSON from the first parseable content item (handles multi-block MCP responses) */
function tryParseJSON(result: MCPToolResult): unknown | null {
  for (const item of result.content) {
    if (item.text) {
      try { return JSON.parse(item.text); } catch { /* try next item */ }
    }
  }
  try { return JSON.parse(result.text); } catch { return null; }
}

describe('E2E-10: Multi-Profile Error Handling', () => {
  let mcp: MCPClient;

  beforeAll(async () => {
    mcp = new MCPClient({
      timeoutMs: 60_000,
    });
    await mcp.start();
  }, 60_000);

  afterAll(async () => {
    await mcp.stop();
  }, 30_000);

  // ── 10a: Invalid profile name ────────────────────────────────────────────────
  test('10a: Invalid profile name returns error', async () => {
    const port = getFixturePort();

    console.error('[10a] Testing invalid profile name: NonExistent_Profile_XYZ');

    try {
      const result = await mcp.callTool('navigate', {
        url: `http://localhost:${port}/site-a`,
        profileDirectory: 'NonExistent_Profile_XYZ',
      });

      // Tool returned without throwing — check for isError in result
      const isError = result.raw?.isError === true;
      const hasErrorMsg =
        result.text.toLowerCase().includes('not found') ||
        result.text.toLowerCase().includes('error');

      expect(isError || hasErrorMsg).toBe(true);
      console.error('[10a] Tool returned error in result (not thrown)');
    } catch (err) {
      // JSON-RPC level error thrown by callTool
      expect((err as Error).message).toMatch(/not found|NonExistent|error/i);
      console.error('[10a] Tool threw error as expected:', (err as Error).message);
    }

    // Verify server is still alive (did not crash)
    const statusResult = await mcp.callTool('oc_profile_status', {});
    expect(statusResult.text).toBeDefined();
    console.error('[10a] Server still responsive after invalid profile request');
  }, 60_000);

  // ── 10b: Pool handles maximum profiles ──────────────────────────────────────
  test('10b: Pool handles maximum profiles', async () => {
    const port = getFixturePort();

    // Launch Chrome instances for up to maxInstances (5) real profiles
    const profiles = ['Default', 'Profile 1', 'Profile 2', 'Profile 3', 'Profile 4'];

    for (const profile of profiles) {
      console.error(`[10b] Launching profile: "${profile}"`);
      try {
        const result = await mcp.callTool(
          'navigate',
          {
            url: `http://localhost:${port}/site-a`,
            profileDirectory: profile,
          },
          60_000,
        );
        expect(result.text).toBeDefined();
        console.error(`[10b] Profile "${profile}" launched OK`);
      } catch (err) {
        // Profile may not exist on this machine — log and continue
        console.error(`[10b] Profile "${profile}" unavailable: ${(err as Error).message}`);
      }
      await scaledSleep(3000);
    }

    // Verify profile status shows active instances
    const status = await mcp.callTool('oc_profile_status', {});
    expect(status.text).toBeDefined();
    console.error(`[10b] oc_profile_status: ${status.text.substring(0, 200)}`);

    // At minimum, at least one profile must have been successfully launched
    // (the test host always has a Default profile)
    expect(status.text.length).toBeGreaterThan(0);
    console.error('[10b] Pool capacity test complete — all available profiles handled');
  }, 300_000);

  // ── 10c: Profile error isolation ────────────────────────────────────────────
  test('10c: Profile error isolation', async () => {
    const port = getFixturePort();

    // Navigate profile A (Default) to site-a
    console.error('[10c] Navigating Default profile to site-a');
    const resultA = await mcp.callTool(
      'navigate',
      {
        url: `http://localhost:${port}/site-a`,
        profileDirectory: 'Default',
      },
      60_000,
    );
    expect(resultA.text).toBeDefined();
    const dataA = tryParseJSON(resultA) as Record<string, unknown> | null;
    if (!dataA || !dataA.tabId) {
      console.error('[10c] Default profile not available in this environment — skipping test');
      return;
    }
    const tabIdA: string = dataA.tabId as string;
    expect(tabIdA).toBeTruthy();
    console.error(`[10c] Default profile tabId=${tabIdA}`);

    await scaledSleep(2000);

    // Navigate profile B (Profile 1) to site-b
    console.error('[10c] Navigating Profile 1 to site-b');
    const resultB = await mcp.callTool(
      'navigate',
      {
        url: `http://localhost:${port}/site-b`,
        profileDirectory: 'Profile 1',
      },
      60_000,
    );
    expect(resultB.text).toBeDefined();
    const dataB = tryParseJSON(resultB) as Record<string, unknown> | null;
    if (!dataB || !dataB.tabId) {
      console.error('[10c] Profile 1 not available in this environment — skipping test');
      return;
    }
    const tabIdB: string = dataB.tabId as string;
    expect(tabIdB).toBeTruthy();
    console.error(`[10c] Profile 1 tabId=${tabIdB}`);

    await scaledSleep(2000);

    // Trigger a JS error in profile A — chrome:// URLs are blocked by navigate,
    // so we use javascript_tool to throw a deliberate runtime error instead.
    console.error('[10c] Triggering deliberate JS error in Default profile');
    try {
      await mcp.callTool('javascript_tool', {
        code: 'throw new Error("deliberate crash test")',
        tabId: tabIdA,
      });
    } catch {
      // Expected — the JS error may surface as a tool error
    }

    await scaledSleep(1000);

    // Profile B must still be functional after the error in profile A
    console.error('[10c] Verifying Profile 1 is still responsive');
    const pageB = await mcp.callTool('read_page', { tabId: tabIdB });
    expect(pageB.text).toBeDefined();
    expect(pageB.text.length).toBeGreaterThan(0);
    console.error(`[10c] Profile B unaffected by Profile A error — ${pageB.text.length} chars read`);

    // Profile A itself should also still be usable (error was in JS, not the browser)
    console.error('[10c] Verifying Default profile is still responsive');
    const pageA = await mcp.callTool('read_page', { tabId: tabIdA });
    expect(pageA.text).toBeDefined();
    console.error(`[10c] Default profile still responsive — ${pageA.text.length} chars read`);
  }, 120_000);

  // ── 10d: Omitted profile falls back to default ───────────────────────────────
  test('10d: Omitted profile falls back to default', async () => {
    const port = getFixturePort();

    // Navigate WITHOUT profileDirectory — should use the default (non-profile) worker
    console.error('[10d] Navigating without profileDirectory');
    const result = await mcp.callTool('navigate', {
      url: `http://localhost:${port}/site-a`,
    });

    expect(result.text).toBeDefined();
    const data = tryParseJSON(result) as Record<string, unknown> | null;
    expect(data).not.toBeNull();

    // Navigation must succeed
    expect(data!.tabId).toBeTruthy();
    console.error(`[10d] Navigation succeeded — tabId=${data!.tabId} workerId=${data!.workerId}`);

    // The default worker should NOT carry a "profile:" prefix in its workerId
    if (data!.workerId) {
      expect(data!.workerId).not.toMatch(/^profile:/);
      console.error(`[10d] workerId="${data!.workerId}" — confirmed not a profile worker`);
    }

    // Verify the page is actually reachable
    const page = await mcp.callTool('read_page', { tabId: data!.tabId as string });
    expect(page.text).toBeDefined();
    expect(page.text.length).toBeGreaterThan(0);
    console.error(`[10d] Default fallback read_page OK — ${page.text.length} chars`);
  }, 60_000);
});
