import { describe, it, expect, beforeEach, vi } from 'vitest';
import { RatingUpdateService } from './RatingUpdateService';
import type { IUserRatingRepository } from '../repositories/IUserRatingRepository';
import type { IRatingEventRepository } from '../repositories/IRatingEventRepository';
import { UserRating } from '../value-objects/UserRating';
import { RatingEvent } from '../entities/RatingEvent';
import { RatingEventId } from '../value-objects/RatingEventId';
import { RatingDimensionKey } from '../value-objects/RatingDimensionKey';
import { RatingDelta } from '../value-objects/RatingDelta';

describe('RatingUpdateService - Slice 7 Evolution', () => {
  let service: RatingUpdateService;
  let userRatingRepository: any;
  let ratingEventRepository: any;

  beforeEach(() => {
    userRatingRepository = {
      findByUserId: vi.fn(),
      save: vi.fn(),
    };

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

    service = new RatingUpdateService(userRatingRepository, ratingEventRepository);
  });

  describe('recordRaceRatingEvents - Ledger-based approach', () => {
    it('should record race events and update snapshots', async () => {
      const raceId = 'race-123';
      const raceResults = [
        {
          userId: 'driver-1',
          startPos: 5,
          finishPos: 2,
          incidents: 1,
          status: 'finished' as const,
        },
        {
          userId: 'driver-2',
          startPos: 3,
          finishPos: 1,
          incidents: 0,
          status: 'finished' as const,
        },
      ];

      // Mock repositories
      ratingEventRepository.save.mockImplementation((event: RatingEvent) => Promise.resolve(event));
      ratingEventRepository.getAllByUserId.mockImplementation((userId: string) => {
        // Return mock events based on user
        if (userId === 'driver-1') {
          return Promise.resolve([
            RatingEvent.create({
              id: RatingEventId.generate(),
              userId: 'driver-1',
              dimension: RatingDimensionKey.create('driving'),
              delta: RatingDelta.create(15),
              weight: 1,
              occurredAt: new Date(),
              createdAt: new Date(),
              source: { type: 'race', id: raceId },
              reason: { code: 'DRIVING_FINISH_STRENGTH_GAIN', summary: 'Test', details: {} },
              visibility: { public: true, redactedFields: [] },
              version: 1,
            }),
          ]);
        }
        return Promise.resolve([
          RatingEvent.create({
            id: RatingEventId.generate(),
            userId: 'driver-2',
            dimension: RatingDimensionKey.create('driving'),
            delta: RatingDelta.create(20),
            weight: 1,
            occurredAt: new Date(),
            createdAt: new Date(),
            source: { type: 'race', id: raceId },
            reason: { code: 'DRIVING_FINISH_STRENGTH_GAIN', summary: 'Test', details: {} },
            visibility: { public: true, redactedFields: [] },
            version: 1,
          }),
        ]);
      });

      userRatingRepository.save.mockImplementation((rating: UserRating) => Promise.resolve(rating));

      const result = await service.recordRaceRatingEvents(raceId, raceResults);

      expect(result.success).toBe(true);
      expect(result.eventsCreated).toBeGreaterThan(0);
      expect(result.driversUpdated).toEqual(['driver-1', 'driver-2']);
      
      // Verify events were saved
      expect(ratingEventRepository.save).toHaveBeenCalled();
      
      // Verify snapshots were recomputed
      expect(ratingEventRepository.getAllByUserId).toHaveBeenCalledWith('driver-1');
      expect(ratingEventRepository.getAllByUserId).toHaveBeenCalledWith('driver-2');
      expect(userRatingRepository.save).toHaveBeenCalledTimes(2);
    });

    it('should handle empty race results gracefully', async () => {
      const result = await service.recordRaceRatingEvents('race-123', []);

      expect(result.success).toBe(true);
      expect(result.eventsCreated).toBe(0);
      expect(result.driversUpdated).toEqual([]);
    });

    it('should return failure on repository errors', async () => {
      ratingEventRepository.save.mockRejectedValue(new Error('Database error'));

      const result = await service.recordRaceRatingEvents('race-123', [
        {
          userId: 'driver-1',
          startPos: 5,
          finishPos: 2,
          incidents: 1,
          status: 'finished' as const,
        },
      ]);

      expect(result.success).toBe(false);
      expect(result.eventsCreated).toBe(0);
      expect(result.driversUpdated).toEqual([]);
    });

    it('should handle DNF status correctly', async () => {
      const raceResults = [
        {
          userId: 'driver-1',
          startPos: 5,
          finishPos: 10,
          incidents: 2,
          status: 'dnf' as const,
        },
      ];

      ratingEventRepository.save.mockImplementation((event: RatingEvent) => Promise.resolve(event));
      ratingEventRepository.getAllByUserId.mockResolvedValue([]);
      userRatingRepository.save.mockImplementation((rating: UserRating) => Promise.resolve(rating));

      const result = await service.recordRaceRatingEvents('race-123', raceResults);

      expect(result.success).toBe(true);
      expect(result.eventsCreated).toBeGreaterThan(0);
      
      // Verify DNF penalty event was created
      const savedEvents = ratingEventRepository.save.mock.calls.map(call => call[0]);
      const hasDnfPenalty = savedEvents.some((event: any) => 
        event.reason.code === 'DRIVING_DNF_PENALTY'
      );
      expect(hasDnfPenalty).toBe(true);
    });
  });

  describe('updateDriverRatingsAfterRace - Backward compatibility', () => {
    it('should delegate to new ledger-based approach', async () => {
      const driverResults = [
        {
          driverId: 'driver-1',
          position: 2,
          totalDrivers: 10,
          incidents: 1,
          startPosition: 5,
        },
      ];

      // Mock the new method to succeed
      const recordSpy = vi.spyOn(service, 'recordRaceRatingEvents').mockResolvedValue({
        success: true,
        eventsCreated: 2,
        driversUpdated: ['driver-1'],
      });

      await service.updateDriverRatingsAfterRace(driverResults);

      expect(recordSpy).toHaveBeenCalled();
      
      recordSpy.mockRestore();
    });

    it('should throw error when ledger approach fails', async () => {
      const driverResults = [
        {
          driverId: 'driver-1',
          position: 2,
          totalDrivers: 10,
          incidents: 1,
          startPosition: 5,
        },
      ];

      // Mock the new method to fail
      const recordSpy = vi.spyOn(service, 'recordRaceRatingEvents').mockResolvedValue({
        success: false,
        eventsCreated: 0,
        driversUpdated: [],
      });

      await expect(service.updateDriverRatingsAfterRace(driverResults)).rejects.toThrow(
        'Failed to update ratings via event system'
      );
      
      recordSpy.mockRestore();
    });
  });

  describe('updateTrustScore - Ledger-based', () => {
    it('should create trust event and update snapshot', async () => {
      const userId = 'user-1';
      const trustChange = 10;

      ratingEventRepository.save.mockImplementation((event: RatingEvent) => Promise.resolve(event));
      ratingEventRepository.getAllByUserId.mockResolvedValue([
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: userId,
          dimension: RatingDimensionKey.create('adminTrust'),
          delta: RatingDelta.create(10),
          weight: 1,
          occurredAt: new Date(),
          createdAt: new Date(),
          source: { type: 'manualAdjustment', id: 'trust-test' },
          reason: { code: 'TRUST_BONUS', summary: 'Test', details: {} },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        }),
      ]);
      userRatingRepository.save.mockImplementation((rating: UserRating) => Promise.resolve(rating));

      await service.updateTrustScore(userId, trustChange);

      expect(ratingEventRepository.save).toHaveBeenCalled();
      expect(userRatingRepository.save).toHaveBeenCalled();
    });

    it('should handle negative trust changes', async () => {
      const userId = 'user-1';
      const trustChange = -5;

      ratingEventRepository.save.mockImplementation((event: RatingEvent) => Promise.resolve(event));
      ratingEventRepository.getAllByUserId.mockResolvedValue([]);
      userRatingRepository.save.mockImplementation((rating: UserRating) => Promise.resolve(rating));

      await service.updateTrustScore(userId, trustChange);

      const savedEvent = ratingEventRepository.save.mock.calls[0][0];
      expect(savedEvent.reason.code).toBe('TRUST_PENALTY');
      expect(savedEvent.delta.value).toBe(-5);
    });
  });

  describe('updateStewardRating - Ledger-based', () => {
    it('should create steward event and update snapshot', async () => {
      const stewardId = 'steward-1';
      const ratingChange = 8;

      ratingEventRepository.save.mockImplementation((event: RatingEvent) => Promise.resolve(event));
      ratingEventRepository.getAllByUserId.mockResolvedValue([
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: stewardId,
          dimension: RatingDimensionKey.create('adminTrust'),
          delta: RatingDelta.create(8),
          weight: 1,
          occurredAt: new Date(),
          createdAt: new Date(),
          source: { type: 'manualAdjustment', id: 'steward-test' },
          reason: { code: 'STEWARD_BONUS', summary: 'Test', details: {} },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        }),
      ]);
      userRatingRepository.save.mockImplementation((rating: UserRating) => Promise.resolve(rating));

      await service.updateStewardRating(stewardId, ratingChange);

      expect(ratingEventRepository.save).toHaveBeenCalled();
      expect(userRatingRepository.save).toHaveBeenCalled();
    });

    it('should handle negative steward rating changes', async () => {
      const stewardId = 'steward-1';
      const ratingChange = -3;

      ratingEventRepository.save.mockImplementation((event: RatingEvent) => Promise.resolve(event));
      ratingEventRepository.getAllByUserId.mockResolvedValue([]);
      userRatingRepository.save.mockImplementation((rating: UserRating) => Promise.resolve(rating));

      await service.updateStewardRating(stewardId, ratingChange);

      const savedEvent = ratingEventRepository.save.mock.calls[0][0];
      expect(savedEvent.reason.code).toBe('STEWARD_PENALTY');
      expect(savedEvent.delta.value).toBe(-3);
    });
  });
});