/// <reference types="jest" />
/**
 * Tests for Navigate Tool - Stealth (CDP-free) mode
 */

import { createMockSessionManager } from '../utils/mock-session';
import { createMockPage } from '../utils/mock-cdp';
import { parseResultJSON } from '../utils/test-helpers';

// Mock the session manager module
jest.mock('../../src/session-manager', () => ({
  getSessionManager: jest.fn(),
}));

// Mock smart-goto
import type { SmartGotoResult } from '../../src/utils/smart-goto';

const mockSmartGotoFn = jest.fn<Promise<SmartGotoResult>, [any, string, any?]>(
  async (page, url, opts) => {
    await page.goto(url, opts);
    return { response: null };
  },
);
jest.mock('../../src/utils/smart-goto', () => ({
  smartGoto: mockSmartGotoFn,
}));

import { getSessionManager } from '../../src/session-manager';

describe('NavigateTool - Stealth Mode', () => {
  let mockSessionManager: ReturnType<typeof createMockSessionManager>;
  let testSessionId: string;

  const getNavigateHandler = async () => {
    jest.resetModules();
    jest.doMock('../../src/session-manager', () => ({
      getSessionManager: () => mockSessionManager,
    }));
    jest.doMock('../../src/utils/smart-goto', () => ({
      smartGoto: mockSmartGotoFn,
    }));
    const { registerNavigateTool } = await import('../../src/tools/navigate');

    const tools: Map<string, { handler: (sessionId: string, args: Record<string, unknown>) => Promise<unknown> }> = new Map();
    const mockServer = {
      registerTool: (name: string, handler: unknown) => {
        tools.set(name, { handler: handler as (sessionId: string, args: Record<string, unknown>) => Promise<unknown> });
      },
    };

    registerNavigateTool(mockServer as unknown as Parameters<typeof registerNavigateTool>[0]);
    return tools.get('navigate')!.handler;
  };

  beforeEach(() => {
    mockSessionManager = createMockSessionManager();
    (getSessionManager as jest.Mock).mockReturnValue(mockSessionManager);
    testSessionId = 'test-session-stealth';

    // Add createTargetStealth mock to the session manager
    (mockSessionManager as any).createTargetStealth = jest.fn().mockImplementation(
      async (sessionId: string, url: string, workerId?: string, settleMs?: number) => {
        const resolvedWorkerId = workerId || 'default';
        const targetId = `stealth-target-${Date.now()}-${Math.random().toString(36).slice(2)}`;
        const page = createMockPage({ url, targetId });
        return { targetId, page, workerId: resolvedWorkerId };
      }
    );
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('stealth parameter', () => {
    test('stealth=true uses createTargetStealth instead of createTarget', async () => {
      const handler = await getNavigateHandler();

      await handler(testSessionId, {
        url: 'https://example.com',
        stealth: true,
      });

      expect((mockSessionManager as any).createTargetStealth).toHaveBeenCalledTimes(1);
      expect(mockSessionManager.createTarget).not.toHaveBeenCalled();
    });

    test('stealth=false (or absent) uses normal createTarget', async () => {
      const handler = await getNavigateHandler();

      await handler(testSessionId, {
        url: 'https://example.com',
      });

      expect(mockSessionManager.createTarget).toHaveBeenCalledTimes(1);
      expect((mockSessionManager as any).createTargetStealth).not.toHaveBeenCalled();
    });

    test('stealth mode passes default settleMs of 8000', async () => {
      const handler = await getNavigateHandler();

      await handler(testSessionId, {
        url: 'https://example.com',
        stealth: true,
      });

      expect((mockSessionManager as any).createTargetStealth).toHaveBeenCalledWith(
        testSessionId,
        'https://example.com',
        undefined,
        8000,
        undefined
      );
    });

    test('stealth mode passes custom stealthSettleMs', async () => {
      const handler = await getNavigateHandler();

      await handler(testSessionId, {
        url: 'https://example.com',
        stealth: true,
        stealthSettleMs: 10000,
      });

      expect((mockSessionManager as any).createTargetStealth).toHaveBeenCalledWith(
        testSessionId,
        'https://example.com',
        undefined,
        10000,
        undefined
      );
    });

    test('stealthSettleMs is clamped to minimum 1000', async () => {
      const handler = await getNavigateHandler();

      await handler(testSessionId, {
        url: 'https://example.com',
        stealth: true,
        stealthSettleMs: 100,
      });

      expect((mockSessionManager as any).createTargetStealth).toHaveBeenCalledWith(
        testSessionId,
        'https://example.com',
        undefined,
        1000,
        undefined
      );
    });

    test('stealthSettleMs is clamped to maximum 30000', async () => {
      const handler = await getNavigateHandler();

      await handler(testSessionId, {
        url: 'https://example.com',
        stealth: true,
        stealthSettleMs: 999999,
      });

      expect((mockSessionManager as any).createTargetStealth).toHaveBeenCalledWith(
        testSessionId,
        'https://example.com',
        undefined,
        30000,
        undefined
      );
    });

    test('stealth mode response includes standard fields', async () => {
      const handler = await getNavigateHandler();

      const result = await handler(testSessionId, {
        url: 'https://example.com',
        stealth: true,
      });

      const parsed = parseResultJSON(result as any) as Record<string, unknown>;

      expect(parsed).toMatchObject({
        action: 'navigate',
        tabId: expect.any(String),
        workerId: expect.any(String),
        created: true,
        stealth: true,
      });
      expect(typeof parsed['url']).toBe('string');
      expect(typeof parsed['title']).toBe('string');
    });

    test('non-stealth response does not include stealth field', async () => {
      const handler = await getNavigateHandler();
      const result = await handler(testSessionId, {
        url: 'https://example.com',
      });
      const parsed = parseResultJSON(result as any) as Record<string, unknown>;
      expect(parsed).not.toHaveProperty('stealth');
    });

    test('stealth mode passes workerId to createTargetStealth', async () => {
      const handler = await getNavigateHandler();

      await handler(testSessionId, {
        url: 'https://example.com',
        stealth: true,
        workerId: 'worker-1',
      });

      expect((mockSessionManager as any).createTargetStealth).toHaveBeenCalledWith(
        testSessionId,
        'https://example.com',
        'worker-1',
        8000,
        undefined
      );
    });

    test('stealth mode adds https:// prefix when missing', async () => {
      const handler = await getNavigateHandler();

      await handler(testSessionId, {
        url: 'example.com',
        stealth: true,
      });

      expect((mockSessionManager as any).createTargetStealth).toHaveBeenCalledWith(
        testSessionId,
        'https://example.com',
        undefined,
        8000,
        undefined
      );
    });
  });

  describe('stealth bypasses tab reuse (#286)', () => {
    test('stealth=true skips tab reuse when worker has 1 existing tab', async () => {
      // Setup: create a session with an existing tab in the default worker
      await mockSessionManager.createTarget(testSessionId, 'https://already-open.com');

      const handler = await getNavigateHandler();

      await handler(testSessionId, {
        url: 'https://turnstile-protected.com',
        stealth: true,
      });

      // Should use createTargetStealth, NOT smartGoto (tab reuse)
      expect((mockSessionManager as any).createTargetStealth).toHaveBeenCalledTimes(1);
      expect((mockSessionManager as any).createTargetStealth).toHaveBeenCalledWith(
        testSessionId,
        'https://turnstile-protected.com',
        undefined,
        8000,
        undefined
      );
      expect(mockSmartGotoFn).not.toHaveBeenCalled();
    });

    test('stealth=false still reuses existing tab (tab reuse unchanged)', async () => {
      // Setup: create a session with an existing tab in the default worker
      await mockSessionManager.createTarget(testSessionId, 'https://already-open.com');

      const handler = await getNavigateHandler();

      await handler(testSessionId, {
        url: 'https://normal-page.com',
        // stealth not set — should reuse existing tab
      });

      // Should reuse tab via smartGoto, NOT create new target
      expect(mockSmartGotoFn).toHaveBeenCalledTimes(1);
      expect((mockSessionManager as any).createTargetStealth).not.toHaveBeenCalled();
      // createTarget should NOT be called again (reuse path)
      expect(mockSessionManager.createTarget).toHaveBeenCalledTimes(1); // only the setup call
    });

    test('stealth=true with custom settleMs skips tab reuse', async () => {
      await mockSessionManager.createTarget(testSessionId, 'https://already-open.com');

      const handler = await getNavigateHandler();

      await handler(testSessionId, {
        url: 'https://turnstile-protected.com',
        stealth: true,
        stealthSettleMs: 15000,
      });

      expect((mockSessionManager as any).createTargetStealth).toHaveBeenCalledWith(
        testSessionId,
        'https://turnstile-protected.com',
        undefined,
        15000,
        undefined
      );
      expect(mockSmartGotoFn).not.toHaveBeenCalled();
    });

    test('stealth=true with specific workerId skips tab reuse on that worker', async () => {
      // Create a worker with an existing tab
      await mockSessionManager.createWorker(testSessionId, { id: 'worker-stealth' });
      await mockSessionManager.createTarget(testSessionId, 'https://already-open.com', 'worker-stealth');

      const handler = await getNavigateHandler();

      await handler(testSessionId, {
        url: 'https://turnstile-protected.com',
        stealth: true,
        workerId: 'worker-stealth',
      });

      expect((mockSessionManager as any).createTargetStealth).toHaveBeenCalledWith(
        testSessionId,
        'https://turnstile-protected.com',
        'worker-stealth',
        8000,
        undefined
      );
      expect(mockSmartGotoFn).not.toHaveBeenCalled();
    });
  });
});

// ─── Stealth defense source verification (#286) ──────────────────────────────

describe('Stealth: navigator.webdriver configurable property (#286)', () => {
  let clientSource: string;

  beforeAll(() => {
    const clientPath = require('path').join(__dirname, '../../src/cdp/client.ts');
    clientSource = require('fs').readFileSync(clientPath, 'utf8');
  });

  test('configurePageDefenses webdriver override includes configurable: true', () => {
    // Extract the configurePageDefenses method body
    const methodStart = clientSource.indexOf('configurePageDefenses(page: Page)');
    expect(methodStart).toBeGreaterThan(-1);

    const nextMethod = clientSource.indexOf('\n  /**', methodStart + 50);
    const defenseBlock = clientSource.slice(methodStart, nextMethod > methodStart ? nextMethod : undefined);

    // Find the webdriver defineProperty block specifically
    const webdriverStart = defenseBlock.indexOf("navigator, 'webdriver'");
    expect(webdriverStart).toBeGreaterThan(-1);

    // Get the surrounding block (up to closing })
    const blockEnd = defenseBlock.indexOf('}).catch', webdriverStart);
    const webdriverBlock = defenseBlock.slice(webdriverStart, blockEnd);

    expect(webdriverBlock).toContain('configurable: true');
  });

  test('createTargetStealth post-attach webdriver override uses prototype deletion (#446)', () => {
    // Extract the createTargetStealth method body
    const methodStart = clientSource.indexOf('createTargetStealth(');
    expect(methodStart).toBeGreaterThan(-1);

    const methodEnd = clientSource.indexOf('\n  /**', methodStart + 50);
    const stealthBlock = clientSource.slice(methodStart, methodEnd > methodStart ? methodEnd : undefined);

    // The webdriver override now uses prototype-level deletion (less detectable
    // than defineProperty) with a defineProperty fallback for headless mode.
    expect(stealthBlock).toContain('Object.getPrototypeOf(navigator)');
    expect(stealthBlock).toContain('webdriver');
  });
});
