import { beforeEach, describe, expect, it } from 'vitest';
import { ExternalGameRatingProfile } from '../../domain/entities/ExternalGameRatingProfile';
import { ExternalGameRatingRepository } from '../../domain/repositories/ExternalGameRatingRepository';
import { UpsertExternalGameRatingInput } from '../dtos/UpsertExternalGameRatingDto';
import { UpsertExternalGameRatingUseCase } from './UpsertExternalGameRatingUseCase';

// Mock repository for integration test
class MockExternalGameRatingRepository {
  private profiles = new Map<string, ExternalGameRatingProfile>();

  private getKey(userId: string, gameKey: string): string {
    return `${userId}|${gameKey}`;
  }

  async findByUserIdAndGameKey(userId: string, gameKey: string): Promise<ExternalGameRatingProfile | null> {
    return this.profiles.get(this.getKey(userId, gameKey)) || null;
  }

  async findByUserId(userId: string): Promise<ExternalGameRatingProfile[]> {
    return Array.from(this.profiles.values()).filter((p: ExternalGameRatingProfile) => p.userId.toString() === userId);
  }

  async findByGameKey(gameKey: string): Promise<ExternalGameRatingProfile[]> {
    return Array.from(this.profiles.values()).filter((p: ExternalGameRatingProfile) => p.gameKey.toString() === gameKey);
  }

  async save(profile: ExternalGameRatingProfile): Promise<ExternalGameRatingProfile> {
    const key = this.getKey(profile.userId.toString(), profile.gameKey.toString());
    this.profiles.set(key, profile);
    return profile;
  }

  async delete(userId: string, gameKey: string): Promise<boolean> {
    return this.profiles.delete(this.getKey(userId, gameKey));
  }

  async exists(userId: string, gameKey: string): Promise<boolean> {
    return this.profiles.has(this.getKey(userId, gameKey));
  }

  clear(): void {
    this.profiles.clear();
  }
}

/**
 * Integration test for UpsertExternalGameRatingUseCase
 * Tests the full flow from use case to repository
 */
describe('UpsertExternalGameRatingUseCase - Integration', () => {
  let useCase: UpsertExternalGameRatingUseCase;
  let repository: MockExternalGameRatingRepository;

  beforeEach(() => {
    repository = new MockExternalGameRatingRepository();
    useCase = new UpsertExternalGameRatingUseCase(repository as unknown as ExternalGameRatingRepository);
  });

  describe('Full upsert flow', () => {
    it('should create and then update a profile', async () => {
      // Step 1: Create new profile
      const createInput: UpsertExternalGameRatingInput = {
        userId: 'user-123',
        gameKey: 'iracing',
        ratings: [
          { type: 'safety', value: 85.5 },
          { type: 'skill', value: 92.0 },
        ],
        provenance: {
          source: 'iracing',
          lastSyncedAt: '2024-01-01T00:00:00Z',
          verified: false,
        },
      };

      const createResult = await useCase.execute(createInput);

      expect(createResult.success).toBe(true);
      expect(createResult.action).toBe('created');
      expect(createResult.profile.ratingCount).toBe(2);
      expect(createResult.profile.verified).toBe(false);

      // Verify it was saved
      const savedProfile = await repository.findByUserIdAndGameKey('user-123', 'iracing');
      expect(savedProfile).not.toBeNull();
      expect(savedProfile?.ratings.size).toBe(2);

      // Step 2: Update the profile
      const updateInput: UpsertExternalGameRatingInput = {
        userId: 'user-123',
        gameKey: 'iracing',
        ratings: [
          { type: 'safety', value: 90.0 },
          { type: 'skill', value: 95.0 },
          { type: 'consistency', value: 88.0 },
        ],
        provenance: {
          source: 'iracing',
          lastSyncedAt: '2024-01-02T00:00:00Z',
          verified: true,
        },
      };

      const updateResult = await useCase.execute(updateInput);

      expect(updateResult.success).toBe(true);
      expect(updateResult.action).toBe('updated');
      expect(updateResult.profile.ratingCount).toBe(3);
      expect(updateResult.profile.verified).toBe(true);

      // Verify the update was persisted
      const updatedProfile = await repository.findByUserIdAndGameKey('user-123', 'iracing');
      expect(updatedProfile).not.toBeNull();
      expect(updatedProfile?.ratings.size).toBe(3);
      expect(updatedProfile?.getRatingByType('safety')?.value).toBe(90.0);
      expect(updatedProfile?.getRatingByType('consistency')).toBeDefined();
      expect(updatedProfile?.provenance.verified).toBe(true);
    });

    it('should handle multiple users and games', async () => {
      // Create profiles for different users/games
      const inputs: UpsertExternalGameRatingInput[] = [
        {
          userId: 'user-1',
          gameKey: 'iracing',
          ratings: [{ type: 'safety', value: 80.0 }],
          provenance: { source: 'iracing', lastSyncedAt: '2024-01-01T00:00:00Z' },
        },
        {
          userId: 'user-1',
          gameKey: 'assetto',
          ratings: [{ type: 'safety', value: 75.0 }],
          provenance: { source: 'assetto', lastSyncedAt: '2024-01-01T00:00:00Z' },
        },
        {
          userId: 'user-2',
          gameKey: 'iracing',
          ratings: [{ type: 'safety', value: 85.0 }],
          provenance: { source: 'iracing', lastSyncedAt: '2024-01-01T00:00:00Z' },
        },
      ];

      for (const input of inputs) {
        const result = await useCase.execute(input);
        expect(result.success).toBe(true);
      }

      // Verify user-1 has 2 profiles
      const user1Profiles = await repository.findByUserId('user-1');
      expect(user1Profiles).toHaveLength(2);

      // Verify iracing has 2 profiles
      const iracingProfiles = await repository.findByGameKey('iracing');
      expect(iracingProfiles).toHaveLength(2);

      // Verify specific profile
      const specific = await repository.findByUserIdAndGameKey('user-1', 'assetto');
      expect(specific?.getRatingByType('safety')?.value).toBe(75.0);
    });

    it('should handle concurrent updates to same profile', async () => {
      // Initial profile
      const input1: UpsertExternalGameRatingInput = {
        userId: 'user-1',
        gameKey: 'iracing',
        ratings: [{ type: 'safety', value: 80.0 }],
        provenance: { source: 'iracing', lastSyncedAt: '2024-01-01T00:00:00Z' },
      };

      await useCase.execute(input1);

      // Update 1
      const input2: UpsertExternalGameRatingInput = {
        userId: 'user-1',
        gameKey: 'iracing',
        ratings: [{ type: 'safety', value: 85.0 }],
        provenance: { source: 'iracing', lastSyncedAt: '2024-01-02T00:00:00Z' },
      };

      await useCase.execute(input2);

      // Update 2 (should overwrite)
      const input3: UpsertExternalGameRatingInput = {
        userId: 'user-1',
        gameKey: 'iracing',
        ratings: [{ type: 'safety', value: 90.0 }],
        provenance: { source: 'iracing', lastSyncedAt: '2024-01-03T00:00:00Z' },
      };

      await useCase.execute(input3);

      // Verify final state
      const profile = await repository.findByUserIdAndGameKey('user-1', 'iracing');
      expect(profile?.getRatingByType('safety')?.value).toBe(90.0);
      expect(profile?.provenance.lastSyncedAt).toEqual(new Date('2024-01-03T00:00:00Z'));
    });

    it('should handle complex rating updates', async () => {
      // Initial with 2 ratings
      const input1: UpsertExternalGameRatingInput = {
        userId: 'user-1',
        gameKey: 'iracing',
        ratings: [
          { type: 'safety', value: 80.0 },
          { type: 'skill', value: 75.0 },
        ],
        provenance: { source: 'iracing', lastSyncedAt: '2024-01-01T00:00:00Z' },
      };

      await useCase.execute(input1);

      // Update with different set
      const input2: UpsertExternalGameRatingInput = {
        userId: 'user-1',
        gameKey: 'iracing',
        ratings: [
          { type: 'safety', value: 85.0 }, // Updated
          { type: 'consistency', value: 88.0 }, // New
          // skill removed
        ],
        provenance: { source: 'iracing', lastSyncedAt: '2024-01-02T00:00:00Z' },
      };

      const result = await useCase.execute(input2);

      expect(result.profile.ratingCount).toBe(2);
      expect(result.profile.ratingTypes).toEqual(['safety', 'consistency']);
      expect(result.profile.ratingTypes).not.toContain('skill');

      const profile = await repository.findByUserIdAndGameKey('user-1', 'iracing');
      expect(profile?.ratings.size).toBe(2);
      expect(profile?.getRatingByType('safety')?.value).toBe(85.0);
      expect(profile?.getRatingByType('consistency')?.value).toBe(88.0);
      expect(profile?.getRatingByType('skill')).toBeUndefined();
    });
  });

  describe('Repository method integration', () => {
    it('should work with repository methods directly', async () => {
      // Create via use case
      const input: UpsertExternalGameRatingInput = {
        userId: 'user-1',
        gameKey: 'iracing',
        ratings: [{ type: 'safety', value: 80.0 }],
        provenance: { source: 'iracing', lastSyncedAt: '2024-01-01T00:00:00Z' },
      };

      await useCase.execute(input);

      // Test repository methods
      const exists = await repository.exists('user-1', 'iracing');
      expect(exists).toBe(true);

      const allForUser = await repository.findByUserId('user-1');
      expect(allForUser).toHaveLength(1);

      const allForGame = await repository.findByGameKey('iracing');
      expect(allForGame).toHaveLength(1);

      // Delete
      const deleted = await repository.delete('user-1', 'iracing');
      expect(deleted).toBe(true);

      const existsAfterDelete = await repository.exists('user-1', 'iracing');
      expect(existsAfterDelete).toBe(false);
    });
  });
});