import 'reflect-metadata';

import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { DataSource } from 'typeorm';

import { RatingEventOrmEntity } from '@adapters/identity/persistence/typeorm/entities/RatingEventOrmEntity';
import { UserRatingOrmEntity } from '@adapters/identity/persistence/typeorm/entities/UserRatingOrmEntity';

import { TypeOrmRatingEventRepository } from '@adapters/identity/persistence/typeorm/repositories/TypeOrmRatingEventRepository';
import { TypeOrmUserRatingRepository } from '@adapters/identity/persistence/typeorm/repositories/TypeOrmUserRatingRepository';

import { AppendRatingEventsUseCase } from '@core/identity/application/use-cases/AppendRatingEventsUseCase';
import { RecomputeUserRatingSnapshotUseCase } from '@core/identity/application/use-cases/RecomputeUserRatingSnapshotUseCase';

const databaseUrl = process.env.DATABASE_URL;
const describeIfDatabase = databaseUrl ? describe : describe.skip;

describeIfDatabase('TypeORM Identity Rating repositories (postgres slice)', () => {
  let dataSource: DataSource;
  let eventRepo: TypeOrmRatingEventRepository;
  let ratingRepo: TypeOrmUserRatingRepository;
  let appendUseCase: AppendRatingEventsUseCase;
  let recomputeUseCase: RecomputeUserRatingSnapshotUseCase;

  beforeAll(async () => {
    if (!databaseUrl) {
      throw new Error('DATABASE_URL is required to run postgres integration tests');
    }

    dataSource = new DataSource({
      type: 'postgres',
      url: databaseUrl,
      entities: [RatingEventOrmEntity, UserRatingOrmEntity],
      synchronize: true,
    });

    await dataSource.initialize();

    // Initialize repositories
    eventRepo = new TypeOrmRatingEventRepository(dataSource);
    ratingRepo = new TypeOrmUserRatingRepository(dataSource);

    // Initialize use cases
    appendUseCase = new AppendRatingEventsUseCase(eventRepo, ratingRepo);
    recomputeUseCase = new RecomputeUserRatingSnapshotUseCase(eventRepo, ratingRepo);
  });

  afterAll(async () => {
    if (dataSource?.isInitialized) {
      // Clean up test data
      await dataSource.getRepository(RatingEventOrmEntity).clear();
      await dataSource.getRepository(UserRatingOrmEntity).clear();
      await dataSource.destroy();
    }
  });

  it('should complete full flow: append events -> persist -> recompute snapshot', async () => {
    const userId = `test-user-${Date.now()}`;

    // Step 1: Append rating events from race results
    const appendResult = await appendUseCase.execute({
      userId,
      raceId: 'race-integration-test',
      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',
        },
      ],
    });

    // Verify events were saved
    expect(appendResult.events.length).toBeGreaterThan(0);
    expect(appendResult.snapshotUpdated).toBe(true);

    // Step 2: Verify events are in database
    const eventsInDb = await eventRepo.getAllByUserId(userId);
    expect(eventsInDb.length).toBeGreaterThan(0);
    expect(eventsInDb.length).toBe(appendResult.events.length);

    // Step 3: Verify snapshot was created
    const snapshotFromDb = await ratingRepo.findByUserId(userId);
    expect(snapshotFromDb).not.toBeNull();
    expect(snapshotFromDb!.userId).toBe(userId);
    expect(snapshotFromDb!.driver.value).toBeGreaterThan(50); // Should have increased

    // Step 4: Recompute snapshot manually
    const recomputeResult = await recomputeUseCase.execute({ userId });

    // Verify recomputed snapshot
    expect(recomputeResult.snapshot.userId).toBe(userId);
    expect(recomputeResult.snapshot.driver.value).toBeGreaterThan(50);
    expect(recomputeResult.snapshot.driver.sampleSize).toBeGreaterThan(0);

    // Step 5: Verify recomputed snapshot matches what's in DB
    const finalSnapshot = await ratingRepo.findByUserId(userId);
    expect(finalSnapshot!.driver.value).toBe(recomputeResult.snapshot.driver.value);
  });

  it('should handle direct event creation and recompute', async () => {
    const userId = `test-user-direct-${Date.now()}`;

    // Append direct events
    const appendResult = await appendUseCase.execute({
      userId,
      events: [
        {
          userId,
          dimension: 'driving',
          delta: 8,
          sourceType: 'race',
          sourceId: 'race-direct',
          reasonCode: 'DRIVING_FINISH_STRENGTH_GAIN',
          reasonSummary: 'Excellent finish',
        },
        {
          userId,
          dimension: 'driving',
          delta: -2,
          weight: 0.5,
          sourceType: 'penalty',
          sourceId: 'penalty-1',
          reasonCode: 'DRIVING_INCIDENTS_PENALTY',
          reasonSummary: 'Minor incident',
        },
      ],
    });

    expect(appendResult.events).toHaveLength(2);
    expect(appendResult.snapshotUpdated).toBe(true);

    // Verify events
    const events = await eventRepo.getAllByUserId(userId);
    expect(events).toHaveLength(2);

    // Verify snapshot
    const snapshot = await ratingRepo.findByUserId(userId);
    expect(snapshot).not.toBeNull();
    expect(snapshot!.driver.value).toBeGreaterThan(50);
    expect(snapshot!.driver.sampleSize).toBe(2);
  });

  it('should handle empty race results gracefully', async () => {
    const userId = `test-user-empty-${Date.now()}`;

    const result = await appendUseCase.execute({
      userId,
      raceId: 'race-empty',
      raceResults: [],
    });

    expect(result.events).toHaveLength(0);
    expect(result.snapshotUpdated).toBe(false);

    // No snapshot should exist
    const snapshot = await ratingRepo.findByUserId(userId);
    expect(snapshot).toBeNull();
  });

  it('should handle DNF/DNS/DSQ/AFK statuses', async () => {
    const userId = `test-user-status-${Date.now()}`;

    const result = await appendUseCase.execute({
      userId,
      raceId: 'race-status-test',
      raceResults: [
        { position: 5, totalDrivers: 10, startPosition: 3, incidents: 2, fieldStrength: 1500, status: 'dnf' },
        { position: 5, totalDrivers: 10, startPosition: 3, incidents: 0, fieldStrength: 1500, status: 'dns' },
        { position: 5, totalDrivers: 10, startPosition: 3, incidents: 5, fieldStrength: 1500, status: 'dsq' },
        { position: 5, totalDrivers: 10, startPosition: 3, incidents: 1, fieldStrength: 1500, status: 'afk' },
      ],
    });

    expect(result.events.length).toBeGreaterThan(0);
    expect(result.snapshotUpdated).toBe(true);

    // Verify events were created for each status
    const events = await eventRepo.getAllByUserId(userId);
    expect(events.length).toBeGreaterThan(0);

    // Verify snapshot exists and has penalties
    const snapshot = await ratingRepo.findByUserId(userId);
    expect(snapshot).not.toBeNull();
    expect(snapshot!.driver.value).toBeLessThan(50); // Should have decreased due to penalties
  });
});