import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
import { StartAutomationSessionUseCase } from '../../../../packages/application/use-cases/StartAutomationSessionUseCase';
import { IAutomationEngine } from '../../../../packages/application/ports/IAutomationEngine';
import { IBrowserAutomation } from '../../../../packages/application/ports/IBrowserAutomation';
import { ISessionRepository } from '../../../../packages/application/ports/ISessionRepository';
import { AutomationSession } from '../../../../packages/domain/entities/AutomationSession';

describe('StartAutomationSessionUseCase', () => {
  let mockAutomationEngine: {
    executeStep: Mock;
    validateConfiguration: Mock;
  };
  let mockBrowserAutomation: {
    navigateToPage: Mock;
    fillFormField: Mock;
    clickElement: Mock;
    waitForElement: Mock;
    handleModal: Mock;
  };
  let mockSessionRepository: {
    save: Mock;
    findById: Mock;
    update: Mock;
    delete: Mock;
  };
  let useCase: StartAutomationSessionUseCase;

  beforeEach(() => {
    mockAutomationEngine = {
      executeStep: vi.fn(),
      validateConfiguration: vi.fn(),
    };

    mockBrowserAutomation = {
      navigateToPage: vi.fn(),
      fillFormField: vi.fn(),
      clickElement: vi.fn(),
      waitForElement: vi.fn(),
      handleModal: vi.fn(),
    };

    mockSessionRepository = {
      save: vi.fn(),
      findById: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
    };

    useCase = new StartAutomationSessionUseCase(
      mockAutomationEngine as unknown as IAutomationEngine,
      mockBrowserAutomation as unknown as IBrowserAutomation,
      mockSessionRepository as unknown as ISessionRepository
    );
  });

  describe('execute - happy path', () => {
    it('should create and persist a new automation session', async () => {
      const config = {
        sessionName: 'Test Race Session',
        trackId: 'spa',
        carIds: ['dallara-f3'],
      };

      mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
      mockSessionRepository.save.mockResolvedValue(undefined);

      const result = await useCase.execute(config);

      expect(result.sessionId).toBeDefined();
      expect(result.state).toBe('PENDING');
      expect(result.currentStep).toBe(1);
      expect(mockAutomationEngine.validateConfiguration).toHaveBeenCalledWith(config);
      expect(mockSessionRepository.save).toHaveBeenCalledWith(
        expect.objectContaining({
          config,
          currentStep: expect.objectContaining({ value: 1 }),
        })
      );
    });

    it('should return session DTO with correct structure', async () => {
      const config = {
        sessionName: 'Test Race Session',
        trackId: 'spa',
        carIds: ['dallara-f3'],
      };

      mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
      mockSessionRepository.save.mockResolvedValue(undefined);

      const result = await useCase.execute(config);

      expect(result).toMatchObject({
        sessionId: expect.any(String),
        state: 'PENDING',
        currentStep: 1,
        config: {
          sessionName: 'Test Race Session',
          trackId: 'spa',
          carIds: ['dallara-f3'],
        },
      });
      expect(result.startedAt).toBeUndefined();
      expect(result.completedAt).toBeUndefined();
      expect(result.errorMessage).toBeUndefined();
    });

    it('should validate configuration before creating session', async () => {
      const config = {
        sessionName: 'Test Race Session',
        trackId: 'spa',
        carIds: ['dallara-f3'],
      };

      mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
      mockSessionRepository.save.mockResolvedValue(undefined);

      await useCase.execute(config);

      expect(mockAutomationEngine.validateConfiguration).toHaveBeenCalledWith(config);
      expect(mockSessionRepository.save).toHaveBeenCalled();
    });
  });

  describe('execute - validation failures', () => {
    it('should throw error for empty session name', async () => {
      const config = {
        sessionName: '',
        trackId: 'spa',
        carIds: ['dallara-f3'],
      };

      await expect(useCase.execute(config)).rejects.toThrow('Session name cannot be empty');
      expect(mockSessionRepository.save).not.toHaveBeenCalled();
    });

    it('should throw error for missing track ID', async () => {
      const config = {
        sessionName: 'Test Race',
        trackId: '',
        carIds: ['dallara-f3'],
      };

      await expect(useCase.execute(config)).rejects.toThrow('Track ID is required');
      expect(mockSessionRepository.save).not.toHaveBeenCalled();
    });

    it('should throw error for empty car list', async () => {
      const config = {
        sessionName: 'Test Race',
        trackId: 'spa',
        carIds: [],
      };

      await expect(useCase.execute(config)).rejects.toThrow('At least one car must be selected');
      expect(mockSessionRepository.save).not.toHaveBeenCalled();
    });

    it('should throw error when automation engine validation fails', async () => {
      const config = {
        sessionName: 'Test Race',
        trackId: 'invalid-track',
        carIds: ['dallara-f3'],
      };

      mockAutomationEngine.validateConfiguration.mockResolvedValue({
        isValid: false,
        error: 'Invalid track ID: invalid-track',
      });

      await expect(useCase.execute(config)).rejects.toThrow('Invalid track ID: invalid-track');
      expect(mockSessionRepository.save).not.toHaveBeenCalled();
    });

    it('should throw error when automation engine validation rejects', async () => {
      const config = {
        sessionName: 'Test Race',
        trackId: 'spa',
        carIds: ['invalid-car'],
      };

      mockAutomationEngine.validateConfiguration.mockRejectedValue(
        new Error('Validation service unavailable')
      );

      await expect(useCase.execute(config)).rejects.toThrow('Validation service unavailable');
      expect(mockSessionRepository.save).not.toHaveBeenCalled();
    });
  });

  describe('execute - port interactions', () => {
    it('should call automation engine before saving session', async () => {
      const config = {
        sessionName: 'Test Race',
        trackId: 'spa',
        carIds: ['dallara-f3'],
      };

      const callOrder: string[] = [];

      mockAutomationEngine.validateConfiguration.mockImplementation(async () => {
        callOrder.push('validateConfiguration');
        return { isValid: true };
      });

      mockSessionRepository.save.mockImplementation(async () => {
        callOrder.push('save');
      });

      await useCase.execute(config);

      expect(callOrder).toEqual(['validateConfiguration', 'save']);
    });

    it('should persist session with domain entity', async () => {
      const config = {
        sessionName: 'Test Race',
        trackId: 'spa',
        carIds: ['dallara-f3'],
      };

      mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
      mockSessionRepository.save.mockResolvedValue(undefined);

      await useCase.execute(config);

      expect(mockSessionRepository.save).toHaveBeenCalledWith(
        expect.any(AutomationSession)
      );
    });

    it('should throw error when repository save fails', async () => {
      const config = {
        sessionName: 'Test Race',
        trackId: 'spa',
        carIds: ['dallara-f3'],
      };

      mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
      mockSessionRepository.save.mockRejectedValue(new Error('Database connection failed'));

      await expect(useCase.execute(config)).rejects.toThrow('Database connection failed');
    });
  });

  describe('execute - edge cases', () => {
    it('should handle very long session names', async () => {
      const config = {
        sessionName: 'A'.repeat(200),
        trackId: 'spa',
        carIds: ['dallara-f3'],
      };

      mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
      mockSessionRepository.save.mockResolvedValue(undefined);

      const result = await useCase.execute(config);

      expect(result.config.sessionName).toBe('A'.repeat(200));
    });

    it('should handle multiple cars in configuration', async () => {
      const config = {
        sessionName: 'Multi-car Race',
        trackId: 'spa',
        carIds: ['dallara-f3', 'porsche-911-gt3', 'bmw-m4-gt4'],
      };

      mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
      mockSessionRepository.save.mockResolvedValue(undefined);

      const result = await useCase.execute(config);

      expect(result.config.carIds).toEqual(['dallara-f3', 'porsche-911-gt3', 'bmw-m4-gt4']);
    });

    it('should handle special characters in session name', async () => {
      const config = {
        sessionName: 'Test & Race #1 (2025)',
        trackId: 'spa',
        carIds: ['dallara-f3'],
      };

      mockAutomationEngine.validateConfiguration.mockResolvedValue({ isValid: true });
      mockSessionRepository.save.mockResolvedValue(undefined);

      const result = await useCase.execute(config);

      expect(result.config.sessionName).toBe('Test & Race #1 (2025)');
    });
  });
});