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

import { RecomputeUserRatingSnapshotUseCase } from './RecomputeUserRatingSnapshotUseCase';
import { IRatingEventRepository } from '../../domain/repositories/IRatingEventRepository';
import { IUserRatingRepository } from '../../domain/repositories/IUserRatingRepository';
import { UserRating } from '../../domain/value-objects/UserRating';
import { RatingEvent } from '../../domain/entities/RatingEvent';
import { RatingEventId } from '../../domain/value-objects/RatingEventId';
import { RatingDimensionKey } from '../../domain/value-objects/RatingDimensionKey';
import { RatingDelta } from '../../domain/value-objects/RatingDelta';

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

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

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

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

  it('should compute snapshot from empty event list', async () => {
    const useCase = new RecomputeUserRatingSnapshotUseCase(
      mockEventRepo as IRatingEventRepository,
      mockRatingRepo as IUserRatingRepository,
    );

    const result = await useCase.execute({ userId: 'user-1' });

    expect(result.snapshot.userId).toBe('user-1');
    expect(result.snapshot.driver.value).toBe(50); // Default
    expect(mockEventRepo.getAllByUserId).toHaveBeenCalledWith('user-1');
    expect(mockRatingRepo.save).toHaveBeenCalled();
  });

  it('should compute snapshot from events', async () => {
    const events = [
      RatingEvent.create({
        id: RatingEventId.generate(),
        userId: 'user-1',
        dimension: RatingDimensionKey.create('driving'),
        delta: RatingDelta.create(5),
        occurredAt: new Date('2024-01-01T10:00:00Z'),
        createdAt: new Date('2024-01-01T10:00:00Z'),
        source: { type: 'race', id: 'race-1' },
        reason: { code: 'TEST', summary: 'Test', details: {} },
        visibility: { public: true, redactedFields: [] },
        version: 1,
      }),
    ];

    mockEventRepo.getAllByUserId = vi.fn().mockResolvedValue(events);

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

    const result = await useCase.execute({ userId: 'user-1' });

    expect(result.snapshot.userId).toBe('user-1');
    expect(result.snapshot.driver.value).toBeGreaterThan(50); // Should have increased
    expect(mockEventRepo.getAllByUserId).toHaveBeenCalledWith('user-1');
    expect(mockRatingRepo.save).toHaveBeenCalled();
  });

  it('should return proper DTO format', async () => {
    const useCase = new RecomputeUserRatingSnapshotUseCase(
      mockEventRepo as IRatingEventRepository,
      mockRatingRepo as IUserRatingRepository,
    );

    const result = await useCase.execute({ userId: 'user-1' });

    // Check DTO structure
    expect(result.snapshot).toHaveProperty('userId');
    expect(result.snapshot).toHaveProperty('driver');
    expect(result.snapshot).toHaveProperty('admin');
    expect(result.snapshot).toHaveProperty('steward');
    expect(result.snapshot).toHaveProperty('trust');
    expect(result.snapshot).toHaveProperty('fairness');
    expect(result.snapshot).toHaveProperty('overallReputation');
    expect(result.snapshot).toHaveProperty('createdAt');
    expect(result.snapshot).toHaveProperty('updatedAt');

    // Check dimension structure
    expect(result.snapshot.driver).toEqual({
      value: expect.any(Number),
      confidence: expect.any(Number),
      sampleSize: expect.any(Number),
      trend: expect.any(String),
      lastUpdated: expect.any(String),
    });

    // Check dates are ISO strings
    expect(result.snapshot.createdAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
    expect(result.snapshot.updatedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
  });

  it('should handle calculatorVersion in DTO', async () => {
    // Create a rating with calculatorVersion
    const rating = UserRating.create('user-1');
    const updated = rating.updateDriverRating(75);
    
    mockRatingRepo.save = vi.fn().mockResolvedValue(updated);

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

    const result = await useCase.execute({ userId: 'user-1' });

    // Should have calculatorVersion
    expect(result.snapshot.calculatorVersion).toBeDefined();
  });
});