import { RatingEvent } from '../entities/RatingEvent';
import { RatingDelta } from '../value-objects/RatingDelta';
import { RatingDimensionKey } from '../value-objects/RatingDimensionKey';
import { RatingEventId } from '../value-objects/RatingEventId';
import { DrivingRaceFactsDto, DrivingRatingCalculator } from './DrivingRatingCalculator';

describe('DrivingRatingCalculator', () => {
  describe('calculateFromRaceFacts', () => {
    it('should calculate delta for finished race with good performance', () => {
      const facts: DrivingRaceFactsDto = {
        raceId: 'race-123',
        results: [
          {
            userId: 'user-123',
            startPos: 5,
            finishPos: 2,
            incidents: 0,
            status: 'finished',
            sof: 2500,
          },
          {
            userId: 'user-456',
            startPos: 3,
            finishPos: 8,
            incidents: 0,
            status: 'finished',
            sof: 2500,
          },
        ],
      };

      const results = DrivingRatingCalculator.calculateFromRaceFacts(facts);
      expect(results.has('user-123')).toBe(true);
      
      const result = results.get('user-123')!;
      expect(result.userId).toBe('user-123');
      expect(result.delta).toBeGreaterThan(0); // Positive for good performance
      expect(result.events.length).toBeGreaterThan(0);
      
      // Should have performance event
      const performanceEvent = result.events.find(e => e.reasonCode === 'DRIVING_FINISH_STRENGTH_GAIN');
      expect(performanceEvent).toBeDefined();
      expect(performanceEvent?.delta).toBeGreaterThan(0);
    });

    it('should calculate delta for finished race with poor performance', () => {
      const facts: DrivingRaceFactsDto = {
        raceId: 'race-123',
        results: [
          {
            userId: 'user-123',
            startPos: 2,
            finishPos: 8,
            incidents: 0,
            status: 'finished',
            sof: 2500,
          },
          {
            userId: 'user-456',
            startPos: 5,
            finishPos: 2,
            incidents: 0,
            status: 'finished',
            sof: 2500,
          },
        ],
      };

      const results = DrivingRatingCalculator.calculateFromRaceFacts(facts);
      const result = results.get('user-123')!;
      expect(result.delta).toBeLessThan(0); // Negative for poor performance
    });

    it('should add incident penalties', () => {
      const facts: DrivingRaceFactsDto = {
        raceId: 'race-123',
        results: [
          {
            userId: 'user-123',
            startPos: 5,
            finishPos: 5,
            incidents: 3,
            status: 'finished',
            sof: 2500,
          },
        ],
      };

      const results = DrivingRatingCalculator.calculateFromRaceFacts(facts);
      const result = results.get('user-123')!;
      
      const incidentEvent = result.events.find(e => e.reasonCode === 'DRIVING_INCIDENTS_PENALTY');
      expect(incidentEvent).toBeDefined();
      expect(incidentEvent?.delta).toBeLessThan(0);
      expect(result.delta).toBeLessThan(0);
    });

    it('should apply DNS penalty', () => {
      const facts: DrivingRaceFactsDto = {
        raceId: 'race-123',
        results: [
          {
            userId: 'user-123',
            startPos: 5,
            finishPos: 10,
            incidents: 0,
            status: 'dns',
            sof: 2500,
          },
        ],
      };

      const results = DrivingRatingCalculator.calculateFromRaceFacts(facts);
      const result = results.get('user-123')!;
      
      const dnsEvent = result.events.find(e => e.reasonCode === 'DRIVING_DNS_PENALTY');
      expect(dnsEvent).toBeDefined();
      expect(dnsEvent?.delta).toBe(-15);
      expect(result.delta).toBeLessThan(0);
    });

    it('should apply DNF penalty', () => {
      const facts: DrivingRaceFactsDto = {
        raceId: 'race-123',
        results: [
          {
            userId: 'user-123',
            startPos: 5,
            finishPos: 10,
            incidents: 0,
            status: 'dnf',
            sof: 2500,
          },
        ],
      };

      const results = DrivingRatingCalculator.calculateFromRaceFacts(facts);
      const result = results.get('user-123')!;
      
      const dnfEvent = result.events.find(e => e.reasonCode === 'DRIVING_DNF_PENALTY');
      expect(dnfEvent).toBeDefined();
      expect(dnfEvent?.delta).toBe(-10);
    });

    it('should apply DSQ penalty', () => {
      const facts: DrivingRaceFactsDto = {
        raceId: 'race-123',
        results: [
          {
            userId: 'user-123',
            startPos: 5,
            finishPos: 10,
            incidents: 0,
            status: 'dsq',
            sof: 2500,
          },
        ],
      };

      const results = DrivingRatingCalculator.calculateFromRaceFacts(facts);
      const result = results.get('user-123')!;
      
      const dsqEvent = result.events.find(e => e.reasonCode === 'DRIVING_DSQ_PENALTY');
      expect(dsqEvent).toBeDefined();
      expect(dsqEvent?.delta).toBe(-25);
    });

    it('should apply AFK penalty', () => {
      const facts: DrivingRaceFactsDto = {
        raceId: 'race-123',
        results: [
          {
            userId: 'user-123',
            startPos: 5,
            finishPos: 10,
            incidents: 0,
            status: 'afk',
            sof: 2500,
          },
        ],
      };

      const results = DrivingRatingCalculator.calculateFromRaceFacts(facts);
      const result = results.get('user-123')!;
      
      const afkEvent = result.events.find(e => e.reasonCode === 'DRIVING_AFK_PENALTY');
      expect(afkEvent).toBeDefined();
      expect(afkEvent?.delta).toBe(-20);
    });

    it('should calculate positions gained bonus', () => {
      const facts: DrivingRaceFactsDto = {
        raceId: 'race-123',
        results: [
          {
            userId: 'user-123',
            startPos: 10,
            finishPos: 3,
            incidents: 0,
            status: 'finished',
            sof: 2500,
          },
          {
            userId: 'user-456',
            startPos: 5,
            finishPos: 8,
            incidents: 0,
            status: 'finished',
            sof: 2500,
          },
        ],
      };

      const results = DrivingRatingCalculator.calculateFromRaceFacts(facts);
      const result = results.get('user-123')!;
      
      const gainEvent = result.events.find(e => e.reasonCode === 'DRIVING_POSITIONS_GAINED_BONUS');
      expect(gainEvent).toBeDefined();
      expect(gainEvent?.delta).toBeGreaterThan(0);
    });

    it('should handle multiple drivers in race', () => {
      const facts: DrivingRaceFactsDto = {
        raceId: 'race-123',
        results: [
          {
            userId: 'user-123',
            startPos: 5,
            finishPos: 2,
            incidents: 0,
            status: 'finished',
            sof: 2500,
          },
          {
            userId: 'user-456',
            startPos: 3,
            finishPos: 8,
            incidents: 2,
            status: 'finished',
            sof: 2500,
          },
          {
            userId: 'user-789',
            startPos: 5,
            finishPos: 5,
            incidents: 0,
            status: 'dns',
            sof: 2500,
          },
        ],
      };

      const results = DrivingRatingCalculator.calculateFromRaceFacts(facts);
      
      expect(results.has('user-123')).toBe(true);
      expect(results.has('user-456')).toBe(true);
      expect(results.has('user-789')).toBe(true);
      
      // user-123 should have positive delta
      expect(results.get('user-123')!.delta).toBeGreaterThan(0);
      
      // user-456 should have negative delta (poor position + incidents)
      expect(results.get('user-456')!.delta).toBeLessThan(0);
      
      // user-789 should have negative delta (DNS)
      expect(results.get('user-789')!.delta).toBeLessThan(0);
    });

    it('should calculate SoF if not provided', () => {
      const facts: DrivingRaceFactsDto = {
        raceId: 'race-123',
        results: [
          {
            userId: 'user-123',
            startPos: 5,
            finishPos: 2,
            incidents: 0,
            status: 'finished',
            // No sof provided
          },
          {
            userId: 'user-456',
            startPos: 3,
            finishPos: 8,
            incidents: 0,
            status: 'finished',
            // No sof provided
          },
        ],
      };

      const results = DrivingRatingCalculator.calculateFromRaceFacts(facts);
      
      // Should still calculate without errors
      expect(results.size).toBe(2);
      expect(results.get('user-123')!.events.length).toBeGreaterThan(0);
    });

    it('should handle empty results array', () => {
      const facts: DrivingRaceFactsDto = {
        raceId: 'race-123',
        results: [],
      };

      const results = DrivingRatingCalculator.calculateFromRaceFacts(facts);
      expect(results.size).toBe(0);
    });
  });

  describe('calculate', () => {
    it('should sum events with component weights', () => {
      const events: RatingEvent[] = [
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: 'user-123',
          dimension: RatingDimensionKey.create('driving'),
          delta: RatingDelta.create(10),
          occurredAt: new Date(),
          createdAt: new Date(),
          source: { type: 'race', id: 'race-123' },
          reason: {
            code: 'DRIVING_FINISH_STRENGTH_GAIN',
            summary: 'Good finish',
            details: {},
          },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        }),
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: 'user-123',
          dimension: RatingDimensionKey.create('driving'),
          delta: RatingDelta.create(-5),
          occurredAt: new Date(),
          createdAt: new Date(),
          source: { type: 'race', id: 'race-123' },
          reason: {
            code: 'DRIVING_INCIDENTS_PENALTY',
            summary: '1 incident',
            details: {},
          },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        }),
      ];

      const result = DrivingRatingCalculator.calculate(events);
      
      // Should apply weights: 10 * 0.5 + (-5) * 0.3 = 5 - 1.5 = 3.5
      // Then normalized by total weight (1 + 1 = 2)
      expect(result).toBeGreaterThan(0);
      expect(result).toBeLessThan(5);
    });

    it('should handle empty events array', () => {
      const result = DrivingRatingCalculator.calculate([]);
      expect(result).toBe(0);
    });

    it('should apply reliability weight to penalty events', () => {
      const events: RatingEvent[] = [
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: 'user-123',
          dimension: RatingDimensionKey.create('driving'),
          delta: RatingDelta.create(-15),
          occurredAt: new Date(),
          createdAt: new Date(),
          source: { type: 'race', id: 'race-123' },
          reason: {
            code: 'DRIVING_DNS_PENALTY',
            summary: 'Did not start',
            details: {},
          },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        }),
      ];

      const result = DrivingRatingCalculator.calculate(events);
      
      // Should apply reliability weight (0.2)
      expect(result).toBe(-15 * 0.2);
    });

    it('should normalize by total weight', () => {
      const events: RatingEvent[] = [
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: 'user-123',
          dimension: RatingDimensionKey.create('driving'),
          delta: RatingDelta.create(20),
          occurredAt: new Date(),
          createdAt: new Date(),
          source: { type: 'race', id: 'race-123' },
          reason: {
            code: 'DRIVING_FINISH_STRENGTH_GAIN',
            summary: 'Test',
            details: {},
          },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        }),
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: 'user-123',
          dimension: RatingDimensionKey.create('driving'),
          delta: RatingDelta.create(-10),
          occurredAt: new Date(),
          createdAt: new Date(),
          source: { type: 'race', id: 'race-123' },
          reason: {
            code: 'DRIVING_DNS_PENALTY',
            summary: 'Test',
            details: {},
          },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        }),
      ];

      const result = DrivingRatingCalculator.calculate(events);
      
      // 20 * 0.5 + (-10) * 0.2 = 10 - 2 = 8
      // Normalized by (1 + 1) = 2
      // Result = 8 / 2 = 4
      expect(result).toBe(4);
    });

    it('should handle events with custom weights', () => {
      const events: RatingEvent[] = [
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: 'user-123',
          dimension: RatingDimensionKey.create('driving'),
          delta: RatingDelta.create(10),
          weight: 2,
          occurredAt: new Date(),
          createdAt: new Date(),
          source: { type: 'race', id: 'race-123' },
          reason: {
            code: 'DRIVING_FINISH_STRENGTH_GAIN',
            summary: 'Test',
            details: {},
          },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        }),
      ];

      const result = DrivingRatingCalculator.calculate(events);
      
      // Should consider event weight
      expect(result).toBeGreaterThan(0);
    });
  });
});