import { describe, expect, it, vi, beforeEach } from 'vitest';

import { AppendRatingEventsUseCase, AppendRatingEventsInput } from './AppendRatingEventsUseCase';
import { IRatingEventRepository } from '../../domain/repositories/IRatingEventRepository';
import { IUserRatingRepository } from '../../domain/repositories/IUserRatingRepository';
import { RatingEvent } from '../../domain/entities/RatingEvent';
import { UserRating } from '../../domain/value-objects/UserRating';
import { RatingEventId } from '../../domain/value-objects/RatingEventId';

describe('AppendRatingEventsUseCase', () => {
  let mockEventRepo: Partial<IRatingEventRepository>;
  let mockRatingRepo: Partial<IUserRatingRepository>;

  beforeEach(() => {
    mockEventRepo = {
      save: vi.fn(),
      getAllByUserId: vi.fn().mockResolvedValue([]),
    };

    mockRatingRepo = {
      save: vi.fn(),
    };
  });

  it('should be constructed with repositories', () => {
    const useCase = new AppendRatingEventsUseCase(
      mockEventRepo as IRatingEventRepository,
      mockRatingRepo as IUserRatingRepository,
    );
    expect(useCase).toBeInstanceOf(AppendRatingEventsUseCase);
  });

  it('should handle empty input (no events)', async () => {
    const useCase = new AppendRatingEventsUseCase(
      mockEventRepo as IRatingEventRepository,
      mockRatingRepo as IUserRatingRepository,
    );

    const input: AppendRatingEventsInput = {
      userId: 'user-1',
    };

    const result = await useCase.execute(input);

    expect(result.events).toEqual([]);
    expect(result.snapshotUpdated).toBe(false);
    expect(mockEventRepo.save).not.toHaveBeenCalled();
    expect(mockRatingRepo.save).not.toHaveBeenCalled();
  });

  it('should create and save events from direct input', async () => {
    const useCase = new AppendRatingEventsUseCase(
      mockEventRepo as IRatingEventRepository,
      mockRatingRepo as IUserRatingRepository,
    );

    const input: AppendRatingEventsInput = {
      userId: 'user-1',
      events: [
        {
          userId: 'user-1',
          dimension: 'driving',
          delta: 5,
          sourceType: 'race',
          sourceId: 'race-1',
          reasonCode: 'TEST',
          reasonSummary: 'Test event',
        },
      ],
    };

    const result = await useCase.execute(input);

    expect(result.events).toHaveLength(1);
    expect(result.snapshotUpdated).toBe(true);
    expect(mockEventRepo.save).toHaveBeenCalledTimes(1);
    expect(mockRatingRepo.save).toHaveBeenCalledTimes(1);
  });

  it('should create events from race results using factory', async () => {
    const useCase = new AppendRatingEventsUseCase(
      mockEventRepo as IRatingEventRepository,
      mockRatingRepo as IUserRatingRepository,
    );

    const input: AppendRatingEventsInput = {
      userId: 'user-1',
      raceId: 'race-123',
      raceResults: [
        {
          position: 3,
          totalDrivers: 10,
          startPosition: 5,
          incidents: 1,
          fieldStrength: 1500,
          status: 'finished',
        },
      ],
    };

    const result = await useCase.execute(input);

    expect(result.events.length).toBeGreaterThan(0);
    expect(result.snapshotUpdated).toBe(true);
    expect(mockEventRepo.save).toHaveBeenCalled();
    expect(mockRatingRepo.save).toHaveBeenCalled();
  });

  it('should handle multiple race results', async () => {
    const useCase = new AppendRatingEventsUseCase(
      mockEventRepo as IRatingEventRepository,
      mockRatingRepo as IUserRatingRepository,
    );

    const input: AppendRatingEventsInput = {
      userId: 'user-1',
      raceId: 'race-123',
      raceResults: [
        { position: 3, totalDrivers: 10, startPosition: 5, incidents: 1, fieldStrength: 1500, status: 'finished' },
        { position: 1, totalDrivers: 10, startPosition: 2, incidents: 0, fieldStrength: 1500, status: 'finished' },
      ],
    };

    const result = await useCase.execute(input);

    expect(result.events.length).toBeGreaterThan(2); // Multiple events per race
    expect(result.snapshotUpdated).toBe(true);
  });

  it('should handle DNF status', async () => {
    const useCase = new AppendRatingEventsUseCase(
      mockEventRepo as IRatingEventRepository,
      mockRatingRepo as IUserRatingRepository,
    );

    const input: AppendRatingEventsInput = {
      userId: 'user-1',
      raceId: 'race-123',
      raceResults: [
        { position: 5, totalDrivers: 10, startPosition: 3, incidents: 2, fieldStrength: 1500, status: 'dnf' },
      ],
    };

    const result = await useCase.execute(input);

    expect(result.events.length).toBeGreaterThan(0);
    expect(result.snapshotUpdated).toBe(true);
    expect(mockEventRepo.save).toHaveBeenCalled();
  });

  it('should not update snapshot if no events were saved', async () => {
    // Mock save to throw or do nothing
    const saveMock = vi.fn().mockResolvedValue({});
    const getAllMock = vi.fn().mockResolvedValue([]);
    
    mockEventRepo = {
      save: saveMock,
      getAllByUserId: getAllMock,
    };

    const useCase = new AppendRatingEventsUseCase(
      mockEventRepo as IRatingEventRepository,
      mockRatingRepo as IUserRatingRepository,
    );

    const input: AppendRatingEventsInput = {
      userId: 'user-1',
      events: [], // Empty
    };

    const result = await useCase.execute(input);

    expect(result.events).toEqual([]);
    expect(result.snapshotUpdated).toBe(false);
    expect(mockRatingRepo.save).not.toHaveBeenCalled();
  });
});