import 'reflect-metadata';

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

import { Game } from '@core/racing/domain/entities/Game';
import { LeagueWallet } from '@core/racing/domain/entities/league-wallet/LeagueWallet';
import { Transaction } from '@core/racing/domain/entities/league-wallet/Transaction';
import { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
import { SeasonSponsorship } from '@core/racing/domain/entities/season/SeasonSponsorship';
import { SponsorshipRequest } from '@core/racing/domain/entities/SponsorshipRequest';
import { Money } from '@core/racing/domain/value-objects/Money';
import { SponsorshipPricing } from '@core/racing/domain/value-objects/SponsorshipPricing';

import {
  GameOrmEntity,
  LeagueWalletOrmEntity,
  SeasonSponsorshipOrmEntity,
  SponsorOrmEntity,
  SponsorshipPricingOrmEntity,
  SponsorshipRequestOrmEntity,
  TransactionOrmEntity,
} from '../../../../../../adapters/racing/persistence/typeorm/entities/MissingRacingOrmEntities';

import { MoneyOrmMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/MoneyOrmMapper';
import {
  GameOrmMapper,
  LeagueWalletOrmMapper,
  SeasonSponsorshipOrmMapper,
  SponsorOrmMapper,
  SponsorshipPricingOrmMapper,
  SponsorshipRequestOrmMapper,
  TransactionOrmMapper,
} from '../../../../../../adapters/racing/persistence/typeorm/mappers/CommerceOrmMappers';

import {
  TypeOrmGameRepository,
  TypeOrmLeagueWalletRepository,
  TypeOrmSeasonSponsorshipRepository,
  TypeOrmSponsorRepository,
  TypeOrmSponsorshipPricingRepository,
  TypeOrmSponsorshipRequestRepository,
  TypeOrmTransactionRepository,
} from '../../../../../../adapters/racing/persistence/typeorm/repositories/CommerceTypeOrmRepositories';

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

describeIfDatabase('TypeORM Racing repositories (commerce slice)', () => {
  let dataSource: DataSource;

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

    dataSource = new DataSource({
      type: 'postgres',
      url: databaseUrl,
      entities: [
        GameOrmEntity,
        SponsorOrmEntity,
        LeagueWalletOrmEntity,
        TransactionOrmEntity,
        SponsorshipPricingOrmEntity,
        SponsorshipRequestOrmEntity,
        SeasonSponsorshipOrmEntity,
      ],
      synchronize: true,
    });

    await dataSource.initialize();
  });

  afterAll(async () => {
    if (dataSource?.isInitialized) {
      await dataSource.destroy();
    }
  });

  it('supports: game + sponsor + wallet + transaction + sponsorship objects', async () => {
    const moneyMapper = new MoneyOrmMapper();

    const gameRepo = new TypeOrmGameRepository(
      dataSource.getRepository(GameOrmEntity),
      new GameOrmMapper(),
    );
    const sponsorRepo = new TypeOrmSponsorRepository(
      dataSource.getRepository(SponsorOrmEntity),
      new SponsorOrmMapper(),
    );
    const walletRepo = new TypeOrmLeagueWalletRepository(
      dataSource.getRepository(LeagueWalletOrmEntity),
      new LeagueWalletOrmMapper(moneyMapper),
    );
    const txRepo = new TypeOrmTransactionRepository(
      dataSource.getRepository(TransactionOrmEntity),
      new TransactionOrmMapper(moneyMapper),
    );

    const pricingRepo = new TypeOrmSponsorshipPricingRepository(
      dataSource.getRepository(SponsorshipPricingOrmEntity),
      new SponsorshipPricingOrmMapper(moneyMapper),
    );
    const requestRepo = new TypeOrmSponsorshipRequestRepository(
      dataSource.getRepository(SponsorshipRequestOrmEntity),
      new SponsorshipRequestOrmMapper(moneyMapper),
    );
    const seasonSponsorshipRepo = new TypeOrmSeasonSponsorshipRepository(
      dataSource.getRepository(SeasonSponsorshipOrmEntity),
      new SeasonSponsorshipOrmMapper(moneyMapper),
    );

    const game = Game.create({ id: 'iracing', name: 'iRacing' });
    await dataSource.getRepository(GameOrmEntity).save(new GameOrmMapper().toOrmEntity(game));
    const persistedGame = await gameRepo.findById('iracing');
    expect(persistedGame?.name.toString()).toBe('iRacing');

    const sponsor = Sponsor.create({
      id: '00000000-0000-4000-8000-000000000010',
      name: 'Sponsor Inc',
      contactEmail: 'sponsor@example.com',
      createdAt: new Date('2025-01-01T00:00:00.000Z'),
    });
    await sponsorRepo.create(sponsor);

    const leagueId = '00000000-0000-4000-8000-000000000020';
    const wallet = LeagueWallet.create({
      id: '00000000-0000-4000-8000-000000000030',
      leagueId,
      balance: Money.create(0, 'USD'),
      createdAt: new Date('2025-01-01T00:00:00.000Z'),
      transactionIds: [],
    });
    await walletRepo.create(wallet);

    const tx = Transaction.rehydrate({
      id: '00000000-0000-4000-8000-000000000040',
      walletId: wallet.id.toString(),
      type: 'refund',
      amount: Money.create(10, 'USD'),
      platformFee: Money.create(1, 'USD'),
      netAmount: Money.create(9, 'USD'),
      status: 'completed',
      createdAt: new Date('2025-01-02T00:00:00.000Z'),
    });
    await txRepo.create(tx);

    const walletTxs = await txRepo.findByWalletId(wallet.id.toString());
    expect(walletTxs.map((t) => t.id.toString())).toContain(tx.id.toString());

    const pricing = SponsorshipPricing.create({
      acceptingApplications: true,
      mainSlot: {
        tier: 'main',
        price: Money.create(100, 'USD'),
        benefits: ['Logo'],
        available: true,
        maxSlots: 1,
      },
    });

    await pricingRepo.save('team', 'team-1', pricing);
    const persistedPricing = await pricingRepo.findByEntity('team', 'team-1');
    expect(persistedPricing?.acceptingApplications).toBe(true);

    const request = SponsorshipRequest.create({
      id: '00000000-0000-4000-8000-000000000050',
      sponsorId: sponsor.id.toString(),
      entityType: 'team',
      entityId: 'team-1',
      tier: 'main',
      offeredAmount: Money.create(100, 'USD'),
      message: 'We would like to sponsor you',
    });
    await requestRepo.create(request);

    const pending = await requestRepo.findPendingByEntity('team', 'team-1');
    expect(pending.map((r) => r.id)).toContain(request.id);

    const seasonSponsorship = SeasonSponsorship.create({
      id: '00000000-0000-4000-8000-000000000060',
      seasonId: '00000000-0000-4000-8000-000000000070',
      sponsorId: sponsor.id.toString(),
      tier: 'main',
      pricing: Money.create(100, 'USD'),
    });
    await seasonSponsorshipRepo.create(seasonSponsorship);

    const seasonSponsorships = await seasonSponsorshipRepo.findBySponsorId(sponsor.id.toString());
    expect(seasonSponsorships.map((s) => s.id)).toContain(seasonSponsorship.id);
  });
});