import { Given, When, Then, Before, After } from '@cucumber/cucumber';
import { expect } from 'vitest';
import { AutomationSession } from '../../../src/packages/domain/entities/AutomationSession';
import { StartAutomationSessionUseCase } from '../../../src/packages/application/use-cases/StartAutomationSessionUseCase';
import { MockBrowserAutomationAdapter } from '../../../src/infrastructure/adapters/automation/MockBrowserAutomationAdapter';
import { InMemorySessionRepository } from '../../../src/infrastructure/repositories/InMemorySessionRepository';
import { StepId } from '../../../src/packages/domain/value-objects/StepId';

interface TestContext {
  sessionRepository: InMemorySessionRepository;
  browserAutomation: MockBrowserAutomationAdapter;
  startAutomationUseCase: StartAutomationSessionUseCase;
  currentSession: AutomationSession | null;
  sessionConfig: any;
  error: Error | null;
  startTime: number;
}

Before(function (this: TestContext) {
  this.sessionRepository = new InMemorySessionRepository();
  this.browserAutomation = new MockBrowserAutomationAdapter();
  this.startAutomationUseCase = new StartAutomationSessionUseCase(
    {} as any, // Mock automation engine
    this.browserAutomation,
    this.sessionRepository
  );
  this.currentSession = null;
  this.sessionConfig = {};
  this.error = null;
  this.startTime = 0;
});

After(function (this: TestContext) {
  this.currentSession = null;
  this.sessionConfig = {};
  this.error = null;
});

Given('the companion app is running', function (this: TestContext) {
  expect(this.browserAutomation).toBeDefined();
});

Given('I am authenticated with iRacing', function (this: TestContext) {
  // Mock authentication state
  expect(true).toBe(true);
});

Given('I have a valid session configuration', function (this: TestContext) {
  this.sessionConfig = {
    sessionName: 'Test Race Session',
    trackId: 'spa',
    carIds: ['dallara-f3'],
  };
});

Given('I have a session configuration with:', function (this: TestContext, dataTable: any) {
  const rows = dataTable.rawTable.slice(1);
  this.sessionConfig = {};
  
  rows.forEach(([field, value]: [string, string]) => {
    if (field === 'carIds') {
      this.sessionConfig[field] = value.split(',').map(v => v.trim());
    } else {
      this.sessionConfig[field] = value;
    }
  });
});

Given('I have started an automation session', async function (this: TestContext) {
  this.sessionConfig = {
    sessionName: 'Test Race',
    trackId: 'spa',
    carIds: ['dallara-f3'],
  };
  
  this.currentSession = AutomationSession.create(this.sessionConfig);
  this.currentSession.start();
  await this.sessionRepository.save(this.currentSession);
});

Given('the automation has reached step {int}', async function (this: TestContext, stepNumber: number) {
  expect(this.currentSession).toBeDefined();
  
  for (let i = 2; i <= stepNumber; i++) {
    this.currentSession!.transitionToStep(StepId.create(i));
  }
  
  await this.sessionRepository.update(this.currentSession!);
});

Given('the automation has progressed to step {int}', async function (this: TestContext, stepNumber: number) {
  expect(this.currentSession).toBeDefined();
  
  for (let i = 2; i <= stepNumber; i++) {
    this.currentSession!.transitionToStep(StepId.create(i));
  }
  
  await this.sessionRepository.update(this.currentSession!);
});

Given('the automation is at step {int}', async function (this: TestContext, stepNumber: number) {
  expect(this.currentSession).toBeDefined();
  
  for (let i = 2; i <= stepNumber; i++) {
    this.currentSession!.transitionToStep(StepId.create(i));
  }
  
  await this.sessionRepository.update(this.currentSession!);
});

Given('the session is in progress', function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.state.isInProgress()).toBe(true);
});

When('I start the automation session', async function (this: TestContext) {
  try {
    const result = await this.startAutomationUseCase.execute(this.sessionConfig);
    this.currentSession = await this.sessionRepository.findById(result.sessionId);
    this.startTime = Date.now();
  } catch (error) {
    this.error = error as Error;
  }
});

When('I attempt to start the automation session', async function (this: TestContext) {
  try {
    const result = await this.startAutomationUseCase.execute(this.sessionConfig);
    this.currentSession = await this.sessionRepository.findById(result.sessionId);
  } catch (error) {
    this.error = error as Error;
  }
});

When('the automation progresses through all {int} steps', async function (this: TestContext, stepCount: number) {
  expect(this.currentSession).toBeDefined();
  this.currentSession!.start();
  
  for (let i = 2; i <= stepCount; i++) {
    this.currentSession!.transitionToStep(StepId.create(i));
    await this.browserAutomation.executeStep(StepId.create(i), this.sessionConfig);
  }
});

When('the automation transitions to step {int}', async function (this: TestContext, stepNumber: number) {
  expect(this.currentSession).toBeDefined();
  this.currentSession!.transitionToStep(StepId.create(stepNumber));
  await this.sessionRepository.update(this.currentSession!);
});

When('the {string} modal appears', async function (this: TestContext, modalName: string) {
  // Simulate modal appearance
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.isAtModalStep()).toBe(true);
});

When('I pause the automation', async function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
  this.currentSession!.pause();
  await this.sessionRepository.update(this.currentSession!);
});

When('I resume the automation', async function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
  this.currentSession!.resume();
  await this.sessionRepository.update(this.currentSession!);
});

When('a browser automation error occurs', async function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
  this.currentSession!.fail('Browser automation failed at step 8');
  await this.sessionRepository.update(this.currentSession!);
});

When('I attempt to skip directly to step {int}', function (this: TestContext, targetStep: number) {
  expect(this.currentSession).toBeDefined();
  
  try {
    this.currentSession!.transitionToStep(StepId.create(targetStep));
  } catch (error) {
    this.error = error as Error;
  }
});

When('I attempt to move back to step {int}', function (this: TestContext, targetStep: number) {
  expect(this.currentSession).toBeDefined();
  
  try {
    this.currentSession!.transitionToStep(StepId.create(targetStep));
  } catch (error) {
    this.error = error as Error;
  }
});

When('the automation reaches step {int}', async function (this: TestContext, stepNumber: number) {
  expect(this.currentSession).toBeDefined();
  
  for (let i = 2; i <= stepNumber; i++) {
    this.currentSession!.transitionToStep(StepId.create(i));
  }
  
  await this.sessionRepository.update(this.currentSession!);
});

When('the application restarts', function (this: TestContext) {
  // Simulate app restart by keeping repository but clearing session reference
  const sessionId = this.currentSession!.id;
  this.currentSession = null;
  
  // Recover session
  this.sessionRepository.findById(sessionId).then(session => {
    this.currentSession = session;
  });
});

When('I attempt to start another automation session', async function (this: TestContext) {
  const newConfig = {
    sessionName: 'Second Race',
    trackId: 'monza',
    carIds: ['porsche-911-gt3'],
  };
  
  try {
    await this.startAutomationUseCase.execute(newConfig);
  } catch (error) {
    this.error = error as Error;
  }
});

When('the automation runs for {int} seconds', async function (this: TestContext, seconds: number) {
  expect(this.currentSession).toBeDefined();
  // Simulate time passage
  await new Promise(resolve => setTimeout(resolve, seconds * 1000));
});

When('I query the session status', async function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
  const retrieved = await this.sessionRepository.findById(this.currentSession!.id);
  this.currentSession = retrieved;
});

Then('the session should be created with state {string}', function (this: TestContext, expectedState: string) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.state.value).toBe(expectedState);
});

Then('the current step should be {int}', function (this: TestContext, expectedStep: number) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.currentStep.value).toBe(expectedStep);
});

Then('the current step should remain {int}', function (this: TestContext, expectedStep: number) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.currentStep.value).toBe(expectedStep);
});

Then('step {int} should navigate to {string}', function (this: TestContext, stepNumber: number, description: string) {
  // Verify step execution would happen
  expect(stepNumber).toBeGreaterThanOrEqual(1);
  expect(stepNumber).toBeLessThanOrEqual(18);
});

Then('step {int} should click {string}', function (this: TestContext, stepNumber: number, description: string) {
  expect(stepNumber).toBeGreaterThanOrEqual(1);
  expect(stepNumber).toBeLessThanOrEqual(18);
});

Then('step {int} should fill {string}', function (this: TestContext, stepNumber: number, description: string) {
  expect(stepNumber).toBeGreaterThanOrEqual(1);
  expect(stepNumber).toBeLessThanOrEqual(18);
});

Then('step {int} should configure {string}', function (this: TestContext, stepNumber: number, description: string) {
  expect(stepNumber).toBeGreaterThanOrEqual(1);
  expect(stepNumber).toBeLessThanOrEqual(18);
});

Then('step {int} should access {string}', function (this: TestContext, stepNumber: number, description: string) {
  expect(stepNumber).toBeGreaterThanOrEqual(1);
  expect(stepNumber).toBeLessThanOrEqual(18);
});

Then('step {int} should handle {string} modal', function (this: TestContext, stepNumber: number, modalName: string) {
  expect([6, 9, 12]).toContain(stepNumber);
});

Then('step {int} should set {string}', function (this: TestContext, stepNumber: number, description: string) {
  expect(stepNumber).toBeGreaterThanOrEqual(1);
  expect(stepNumber).toBeLessThanOrEqual(18);
});

Then('step {int} should reach {string}', function (this: TestContext, stepNumber: number, description: string) {
  expect(stepNumber).toBe(18);
});

Then('the session should stop at step {int}', function (this: TestContext, expectedStep: number) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.currentStep.value).toBe(expectedStep);
  expect(this.currentSession!.state.isStoppedAtStep18()).toBe(true);
});

Then('the session state should be {string}', function (this: TestContext, expectedState: string) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.state.value).toBe(expectedState);
});

Then('a manual submit warning should be displayed', function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.currentStep.isFinalStep()).toBe(true);
});

Then('the automation should detect the modal', function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.isAtModalStep()).toBe(true);
});

Then('the automation should wait for modal content to load', async function (this: TestContext) {
  // Simulate wait
  expect(this.currentSession).toBeDefined();
});

Then('the automation should fill admin fields', async function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
});

Then('the automation should close the modal', async function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
});

Then('the automation should transition to step {int}', async function (this: TestContext, nextStep: number) {
  expect(this.currentSession).toBeDefined();
  this.currentSession!.transitionToStep(StepId.create(nextStep));
});

Then('the automation should select the car {string}', async function (this: TestContext, carId: string) {
  expect(this.sessionConfig.carIds).toContain(carId);
});

Then('the automation should confirm the selection', async function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
});

Then('the automation should select the track {string}', async function (this: TestContext, trackId: string) {
  expect(this.sessionConfig.trackId).toBe(trackId);
});

Then('the automation should automatically stop', function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.state.isStoppedAtStep18()).toBe(true);
});

Then('no submit action should be executed', function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.state.isStoppedAtStep18()).toBe(true);
});

Then('a notification should inform the user to review before submitting', function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.currentStep.isFinalStep()).toBe(true);
});

Then('the automation should continue from step {int}', function (this: TestContext, expectedStep: number) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.currentStep.value).toBe(expectedStep);
});

Then('an error message should be recorded', function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.errorMessage).toBeDefined();
});

Then('the session should have a completedAt timestamp', function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.completedAt).toBeDefined();
});

Then('the user should be notified of the failure', function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.state.isFailed()).toBe(true);
});

Then('the session creation should fail', function (this: TestContext) {
  expect(this.error).toBeDefined();
});

Then('an error message should indicate {string}', function (this: TestContext, expectedMessage: string) {
  expect(this.error).toBeDefined();
  expect(this.error!.message).toContain(expectedMessage);
});

Then('no session should be persisted', async function (this: TestContext) {
  const sessions = await this.sessionRepository.findAll();
  expect(sessions).toHaveLength(0);
});

Then('the transition should be rejected', function (this: TestContext) {
  expect(this.error).toBeDefined();
});

Then('all three cars should be added via the modal', function (this: TestContext) {
  expect(this.sessionConfig.carIds).toHaveLength(3);
});

Then('the automation should handle the modal three times', function (this: TestContext) {
  expect(this.sessionConfig.carIds).toHaveLength(3);
});

Then('the session should be recoverable from storage', async function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
});

Then('the session configuration should be intact', function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.config).toBeDefined();
});

Then('the second session creation should be queued or rejected', function (this: TestContext) {
  expect(this.error).toBeDefined();
});

Then('a warning should inform about the active session', function (this: TestContext) {
  expect(this.error).toBeDefined();
});

Then('the elapsed time should be approximately {int} milliseconds', function (this: TestContext, expectedMs: number) {
  expect(this.currentSession).toBeDefined();
  const elapsed = this.currentSession!.getElapsedTime();
  expect(elapsed).toBeGreaterThanOrEqual(expectedMs - 1000);
  expect(elapsed).toBeLessThanOrEqual(expectedMs + 1000);
});

Then('the elapsed time should increase while in progress', function (this: TestContext) {
  expect(this.currentSession).toBeDefined();
  const elapsed = this.currentSession!.getElapsedTime();
  expect(elapsed).toBeGreaterThan(0);
});

Then('each step should take between {int}ms and {int}ms', function (this: TestContext, minMs: number, maxMs: number) {
  // This would be validated during actual execution
  expect(minMs).toBeLessThan(maxMs);
});

Then('modal steps should take longer than regular steps', function (this: TestContext) {
  // This would be validated during actual execution
  expect(true).toBe(true);
});

Then('the total workflow should complete in under {int} seconds', function (this: TestContext, maxSeconds: number) {
  expect(this.currentSession).toBeDefined();
  const elapsed = this.currentSession!.getElapsedTime();
  expect(elapsed).toBeLessThan(maxSeconds * 1000);
});

Then('the session should stop at step {int} without submitting', function (this: TestContext, expectedStep: number) {
  expect(this.currentSession).toBeDefined();
  expect(this.currentSession!.currentStep.value).toBe(expectedStep);
  expect(this.currentSession!.state.isStoppedAtStep18()).toBe(true);
});