import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { BrowserDevToolsAdapter, DevToolsConfig } from '../../../packages/infrastructure/adapters/automation/BrowserDevToolsAdapter';
import { StepId } from '../../../packages/domain/value-objects/StepId';
import {
  IRacingSelectorMap,
  getStepSelectors,
  getStepName,
  isModalStep,
} from '../../../packages/infrastructure/adapters/automation/selectors/IRacingSelectorMap';

// Mock puppeteer-core
vi.mock('puppeteer-core', () => {
  const mockPage = {
    url: vi.fn().mockReturnValue('https://members-ng.iracing.com/web/racing/hosted'),
    goto: vi.fn().mockResolvedValue(undefined),
    $: vi.fn().mockResolvedValue({
      click: vi.fn().mockResolvedValue(undefined),
      type: vi.fn().mockResolvedValue(undefined),
    }),
    click: vi.fn().mockResolvedValue(undefined),
    type: vi.fn().mockResolvedValue(undefined),
    waitForSelector: vi.fn().mockResolvedValue(undefined),
    setDefaultTimeout: vi.fn(),
    screenshot: vi.fn().mockResolvedValue(undefined),
    content: vi.fn().mockResolvedValue('<html></html>'),
    waitForNetworkIdle: vi.fn().mockResolvedValue(undefined),
    evaluate: vi.fn().mockResolvedValue(undefined),
  };

  const mockBrowser = {
    pages: vi.fn().mockResolvedValue([mockPage]),
    disconnect: vi.fn(),
  };

  return {
    default: {
      connect: vi.fn().mockResolvedValue(mockBrowser),
    },
  };
});

// Mock global fetch for CDP endpoint discovery
global.fetch = vi.fn().mockResolvedValue({
  json: vi.fn().mockResolvedValue({
    webSocketDebuggerUrl: 'ws://127.0.0.1:9222/devtools/browser/mock-id',
  }),
});

describe('BrowserDevToolsAdapter', () => {
  let adapter: BrowserDevToolsAdapter;

  beforeEach(() => {
    vi.clearAllMocks();
    adapter = new BrowserDevToolsAdapter({
      debuggingPort: 9222,
      defaultTimeout: 5000,
      typingDelay: 10,
    });
  });

  afterEach(async () => {
    if (adapter.isConnected()) {
      await adapter.disconnect();
    }
  });

  describe('instantiation', () => {
    it('should create adapter with default config', () => {
      const defaultAdapter = new BrowserDevToolsAdapter();
      expect(defaultAdapter).toBeInstanceOf(BrowserDevToolsAdapter);
      expect(defaultAdapter.isConnected()).toBe(false);
    });

    it('should create adapter with custom config', () => {
      const customConfig: DevToolsConfig = {
        debuggingPort: 9333,
        defaultTimeout: 10000,
        typingDelay: 100,
        waitForNetworkIdle: false,
      };
      const customAdapter = new BrowserDevToolsAdapter(customConfig);
      expect(customAdapter).toBeInstanceOf(BrowserDevToolsAdapter);
    });

    it('should create adapter with explicit WebSocket endpoint', () => {
      const wsAdapter = new BrowserDevToolsAdapter({
        browserWSEndpoint: 'ws://127.0.0.1:9222/devtools/browser/test-id',
      });
      expect(wsAdapter).toBeInstanceOf(BrowserDevToolsAdapter);
    });
  });

  describe('connect/disconnect', () => {
    it('should connect to browser via debugging port', async () => {
      await adapter.connect();
      expect(adapter.isConnected()).toBe(true);
    });

    it('should disconnect from browser without closing it', async () => {
      await adapter.connect();
      expect(adapter.isConnected()).toBe(true);
      
      await adapter.disconnect();
      expect(adapter.isConnected()).toBe(false);
    });

    it('should handle multiple connect calls gracefully', async () => {
      await adapter.connect();
      await adapter.connect(); // Should not throw
      expect(adapter.isConnected()).toBe(true);
    });

    it('should handle disconnect when not connected', async () => {
      await adapter.disconnect(); // Should not throw
      expect(adapter.isConnected()).toBe(false);
    });
  });

  describe('navigateToPage', () => {
    beforeEach(async () => {
      await adapter.connect();
    });

    it('should navigate to URL successfully', async () => {
      const result = await adapter.navigateToPage('https://members-ng.iracing.com');
      
      expect(result.success).toBe(true);
      expect(result.url).toBe('https://members-ng.iracing.com');
      expect(result.loadTime).toBeGreaterThanOrEqual(0);
    });

    it('should return error when not connected', async () => {
      await adapter.disconnect();
      
      await expect(adapter.navigateToPage('https://example.com'))
        .rejects.toThrow('Not connected to browser');
    });
  });

  describe('fillFormField', () => {
    beforeEach(async () => {
      await adapter.connect();
    });

    it('should fill form field successfully', async () => {
      const result = await adapter.fillFormField('input[name="sessionName"]', 'Test Session');
      
      expect(result.success).toBe(true);
      expect(result.fieldName).toBe('input[name="sessionName"]');
      expect(result.valueSet).toBe('Test Session');
    });

    it('should return error for non-existent field', async () => {
      // Re-mock to return null for element lookup
      const puppeteer = await import('puppeteer-core');
      const mockBrowser = await puppeteer.default.connect({} as any);
      const pages = await mockBrowser.pages();
      const mockPage = pages[0] as any;
      mockPage.$.mockResolvedValueOnce(null);
      
      const result = await adapter.fillFormField('input[name="nonexistent"]', 'value');
      
      expect(result.success).toBe(false);
      expect(result.error).toContain('Field not found');
    });
  });

  describe('clickElement', () => {
    beforeEach(async () => {
      await adapter.connect();
    });

    it('should click element successfully', async () => {
      const result = await adapter.clickElement('.btn-primary');
      
      expect(result.success).toBe(true);
      expect(result.target).toBe('.btn-primary');
    });
  });

  describe('waitForElement', () => {
    beforeEach(async () => {
      await adapter.connect();
    });

    it('should wait for element and find it', async () => {
      const result = await adapter.waitForElement('#create-race-modal', 5000);
      
      expect(result.success).toBe(true);
      expect(result.found).toBe(true);
      expect(result.target).toBe('#create-race-modal');
    });

    it('should return not found when element does not appear', async () => {
      // Re-mock to throw timeout error
      const puppeteer = await import('puppeteer-core');
      const mockBrowser = await puppeteer.default.connect({} as any);
      const pages = await mockBrowser.pages();
      const mockPage = pages[0] as any;
      mockPage.waitForSelector.mockRejectedValueOnce(new Error('Timeout'));
      
      const result = await adapter.waitForElement('#nonexistent', 100);
      
      expect(result.success).toBe(false);
      expect(result.found).toBe(false);
    });
  });

  describe('handleModal', () => {
    beforeEach(async () => {
      await adapter.connect();
    });

    it('should handle modal for step 6 (SET_ADMINS)', async () => {
      const stepId = StepId.create(6);
      const result = await adapter.handleModal(stepId, 'open');
      
      expect(result.success).toBe(true);
      expect(result.stepId).toBe(6);
      expect(result.action).toBe('open');
    });

    it('should handle modal for step 9 (ADD_CAR)', async () => {
      const stepId = StepId.create(9);
      const result = await adapter.handleModal(stepId, 'close');
      
      expect(result.success).toBe(true);
      expect(result.stepId).toBe(9);
      expect(result.action).toBe('close');
    });

    it('should handle modal for step 12 (ADD_TRACK)', async () => {
      const stepId = StepId.create(12);
      const result = await adapter.handleModal(stepId, 'search');
      
      expect(result.success).toBe(true);
      expect(result.stepId).toBe(12);
    });

    it('should return error for non-modal step', async () => {
      const stepId = StepId.create(4); // RACE_INFORMATION is not a modal step
      const result = await adapter.handleModal(stepId, 'open');
      
      expect(result.success).toBe(false);
      expect(result.error).toContain('not a modal step');
    });

    it('should return error for unknown action', async () => {
      const stepId = StepId.create(6);
      const result = await adapter.handleModal(stepId, 'unknown_action');
      
      expect(result.success).toBe(false);
      expect(result.error).toContain('Unknown modal action');
    });
  });
});

describe('IRacingSelectorMap', () => {
  describe('common selectors', () => {
    it('should have all required common selectors', () => {
      expect(IRacingSelectorMap.common.mainModal).toBeDefined();
      expect(IRacingSelectorMap.common.modalDialog).toBeDefined();
      expect(IRacingSelectorMap.common.modalContent).toBeDefined();
      expect(IRacingSelectorMap.common.checkoutButton).toBeDefined();
      expect(IRacingSelectorMap.common.wizardContainer).toBeDefined();
      expect(IRacingSelectorMap.common.wizardSidebar).toBeDefined();
    });

    it('should have iRacing-specific URLs', () => {
      expect(IRacingSelectorMap.urls.base).toContain('iracing.com');
      expect(IRacingSelectorMap.urls.hostedRacing).toContain('hosted');
    });
  });

  describe('step selectors', () => {
    it('should have selectors for all 18 steps', () => {
      for (let i = 1; i <= 18; i++) {
        expect(IRacingSelectorMap.steps[i]).toBeDefined();
      }
    });

    it('should have wizard navigation for most steps', () => {
      // Steps that have wizard navigation
      const stepsWithWizardNav = [4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18];
      
      for (const stepNum of stepsWithWizardNav) {
        const selectors = IRacingSelectorMap.steps[stepNum];
        expect(selectors.wizardNav || selectors.sidebarLink).toBeDefined();
      }
    });

    it('should have modal selectors for modal steps (6, 9, 12)', () => {
      expect(IRacingSelectorMap.steps[6].modal).toBeDefined();
      expect(IRacingSelectorMap.steps[9].modal).toBeDefined();
      expect(IRacingSelectorMap.steps[12].modal).toBeDefined();
    });

    it('should NOT have checkout button in step 18 (safety)', () => {
      const step18 = IRacingSelectorMap.steps[18];
      expect(step18.buttons?.checkout).toBeUndefined();
    });
  });

  describe('getStepSelectors', () => {
    it('should return selectors for valid step', () => {
      const selectors = getStepSelectors(4);
      expect(selectors).toBeDefined();
      expect(selectors?.container).toBe('#set-session-information');
    });

    it('should return undefined for invalid step', () => {
      const selectors = getStepSelectors(99);
      expect(selectors).toBeUndefined();
    });
  });

  describe('isModalStep', () => {
    it('should return true for modal steps', () => {
      expect(isModalStep(6)).toBe(true);
      expect(isModalStep(9)).toBe(true);
      expect(isModalStep(12)).toBe(true);
    });

    it('should return false for non-modal steps', () => {
      expect(isModalStep(1)).toBe(false);
      expect(isModalStep(4)).toBe(false);
      expect(isModalStep(18)).toBe(false);
    });
  });

  describe('getStepName', () => {
    it('should return correct step names', () => {
      expect(getStepName(1)).toBe('LOGIN');
      expect(getStepName(4)).toBe('RACE_INFORMATION');
      expect(getStepName(6)).toBe('SET_ADMINS');
      expect(getStepName(9)).toBe('ADD_CAR');
      expect(getStepName(12)).toBe('ADD_TRACK');
      expect(getStepName(18)).toBe('TRACK_CONDITIONS');
    });

    it('should return UNKNOWN for invalid step', () => {
      expect(getStepName(99)).toContain('UNKNOWN');
    });
  });
});

describe('Integration: Adapter with SelectorMap', () => {
  let adapter: BrowserDevToolsAdapter;

  beforeEach(async () => {
    adapter = new BrowserDevToolsAdapter();
    await adapter.connect();
  });

  afterEach(async () => {
    await adapter.disconnect();
  });

  it('should use selector map for navigation', async () => {
    const selectors = getStepSelectors(4);
    expect(selectors?.sidebarLink).toBeDefined();
    
    const result = await adapter.clickElement(selectors!.sidebarLink!);
    expect(result.success).toBe(true);
  });

  it('should use selector map for form filling', async () => {
    const selectors = getStepSelectors(4);
    expect(selectors?.fields?.sessionName).toBeDefined();
    
    const result = await adapter.fillFormField(
      selectors!.fields!.sessionName,
      'My Test Session'
    );
    expect(result.success).toBe(true);
  });

  it('should use selector map for modal handling', async () => {
    const stepId = StepId.create(9);
    const selectors = getStepSelectors(9);
    expect(selectors?.modal).toBeDefined();
    
    const result = await adapter.handleModal(stepId, 'open');
    expect(result.success).toBe(true);
  });
});