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

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 { SeasonSponsorship } from '@core/racing/domain/entities/season/SeasonSponsorship';
import { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
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 '../entities/MissingRacingOrmEntities';
import { TypeOrmPersistenceSchemaError } from '../errors/TypeOrmPersistenceSchemaError';
import {
    GameOrmMapper,
    LeagueWalletOrmMapper,
    SeasonSponsorshipOrmMapper,
    SponsorOrmMapper,
    SponsorshipPricingOrmMapper,
    SponsorshipRequestOrmMapper,
    TransactionOrmMapper,
} from './CommerceOrmMappers';
import { MoneyOrmMapper } from './MoneyOrmMapper';

describe('GameOrmMapper', () => {
  it('toDomain uses rehydrate semantics (does not call create)', () => {
    const mapper = new GameOrmMapper();

    const entity = new GameOrmEntity();
    entity.id = 'iracing';
    entity.name = 'iRacing';

    const rehydrateSpy = vi.spyOn(Game, 'rehydrate');
    const createSpy = vi.spyOn(Game, 'create').mockImplementation(() => {
      throw new Error('create-called');
    });

    const domain = mapper.toDomain(entity);

    expect(domain.id.toString()).toBe('iracing');
    expect(createSpy).not.toHaveBeenCalled();
    expect(rehydrateSpy).toHaveBeenCalled();
  });
});

describe('SponsorOrmMapper', () => {
  it('toDomain uses rehydrate semantics (does not call create)', () => {
    const mapper = new SponsorOrmMapper();

    const entity = new SponsorOrmEntity();
    entity.id = '00000000-0000-4000-8000-000000000001';
    entity.name = 'Sponsor One';
    entity.contactEmail = 'a@example.com';
    entity.logoUrl = null;
    entity.websiteUrl = null;
    entity.createdAt = new Date('2025-01-01T00:00:00.000Z');

    const rehydrateSpy = vi.spyOn(Sponsor, 'rehydrate');
    const createSpy = vi.spyOn(Sponsor, 'create').mockImplementation(() => {
      throw new Error('create-called');
    });

    const domain = mapper.toDomain(entity);

    expect(domain.id.toString()).toBe(entity.id);
    expect(createSpy).not.toHaveBeenCalled();
    expect(rehydrateSpy).toHaveBeenCalled();
  });

  it('toDomain validates createdAt is a Date', () => {
    const mapper = new SponsorOrmMapper();

    const entity = new SponsorOrmEntity();
    entity.id = '00000000-0000-4000-8000-000000000001';
    entity.name = 'Sponsor One';
    entity.contactEmail = 'a@example.com';
    entity.logoUrl = null;
    entity.websiteUrl = null;
    entity.createdAt = 'not-a-date' as unknown as Date;

    try {
      mapper.toDomain(entity);
      throw new Error('expected-to-throw');
    } catch (error) {
      expect(error).toBeInstanceOf(TypeOrmPersistenceSchemaError);
      expect(error).toMatchObject({
        entityName: 'Sponsor',
        fieldName: 'createdAt',
        reason: 'not_date',
      });
    }
  });
});

describe('LeagueWalletOrmMapper', () => {
  it('toDomain uses rehydrate semantics', () => {
    const moneyMapper = new MoneyOrmMapper();
    const mapper = new LeagueWalletOrmMapper(moneyMapper);

    const entity = new LeagueWalletOrmEntity();
    entity.id = '00000000-0000-4000-8000-000000000001';
    entity.leagueId = '00000000-0000-4000-8000-000000000002';
    entity.balance = { amount: 10, currency: 'USD' };
    entity.transactionIds = [];
    entity.createdAt = new Date('2025-01-01T00:00:00.000Z');

    const rehydrateSpy = vi.spyOn(LeagueWallet, 'rehydrate');

    const domain = mapper.toDomain(entity);

    expect(domain.id.toString()).toBe(entity.id);
    expect(rehydrateSpy).toHaveBeenCalled();
  });

  it('toDomain validates createdAt is a Date', () => {
    const moneyMapper = new MoneyOrmMapper();
    const mapper = new LeagueWalletOrmMapper(moneyMapper);

    const entity = new LeagueWalletOrmEntity();
    entity.id = '00000000-0000-4000-8000-000000000001';
    entity.leagueId = '00000000-0000-4000-8000-000000000002';
    entity.balance = { amount: 10, currency: 'USD' };
    entity.transactionIds = [];
    entity.createdAt = 'not-a-date' as unknown as Date;

    try {
      mapper.toDomain(entity);
      throw new Error('expected-to-throw');
    } catch (error) {
      expect(error).toBeInstanceOf(TypeOrmPersistenceSchemaError);
      expect(error).toMatchObject({
        entityName: 'LeagueWallet',
        fieldName: 'createdAt',
        reason: 'not_date',
      });
    }
  });
});

describe('TransactionOrmMapper', () => {
  it('toDomain uses rehydrate semantics', () => {
    const moneyMapper = new MoneyOrmMapper();
    const mapper = new TransactionOrmMapper(moneyMapper);

    const entity = new TransactionOrmEntity();
    entity.id = '00000000-0000-4000-8000-000000000001';
    entity.walletId = '00000000-0000-4000-8000-000000000002';
    entity.type = 'refund';
    entity.amount = { amount: 10, currency: 'USD' };
    entity.platformFee = { amount: 1, currency: 'USD' };
    entity.netAmount = { amount: 9, currency: 'USD' };
    entity.status = 'completed';
    entity.createdAt = new Date('2025-01-01T00:00:00.000Z');
    entity.completedAt = null;
    entity.description = null;
    entity.metadata = null;

    const rehydrateSpy = vi.spyOn(Transaction, 'rehydrate');

    const domain = mapper.toDomain(entity);

    expect(domain.id.toString()).toBe(entity.id);
    expect(rehydrateSpy).toHaveBeenCalled();
  });

  it('toDomain validates createdAt is a Date', () => {
    const moneyMapper = new MoneyOrmMapper();
    const mapper = new TransactionOrmMapper(moneyMapper);

    const entity = new TransactionOrmEntity();
    entity.id = '00000000-0000-4000-8000-000000000001';
    entity.walletId = '00000000-0000-4000-8000-000000000002';
    entity.type = 'refund';
    entity.amount = { amount: 10, currency: 'USD' };
    entity.platformFee = { amount: 1, currency: 'USD' };
    entity.netAmount = { amount: 9, currency: 'USD' };
    entity.status = 'completed';
    entity.createdAt = 'not-a-date' as unknown as Date;
    entity.completedAt = null;
    entity.description = null;
    entity.metadata = null;

    try {
      mapper.toDomain(entity);
      throw new Error('expected-to-throw');
    } catch (error) {
      expect(error).toBeInstanceOf(TypeOrmPersistenceSchemaError);
      expect(error).toMatchObject({
        entityName: 'Transaction',
        fieldName: 'createdAt',
        reason: 'not_date',
      });
    }
  });
});

describe('SponsorshipPricingOrmMapper', () => {
  it('round-trips value object via JSON', () => {
    const moneyMapper = new MoneyOrmMapper();
    const mapper = new SponsorshipPricingOrmMapper(moneyMapper);

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

    const orm = mapper.toOrmEntity('team', 'team-1', pricing);
    const rehydrated = mapper.toDomain(orm);

    expect(rehydrated.equals(pricing)).toBe(true);
  });

  it('toDomain validates nested currency enums', () => {
    const mapper = new SponsorshipPricingOrmMapper(new MoneyOrmMapper());

    const entity = new SponsorshipPricingOrmEntity();
    entity.id = 'team:team-1';
    entity.entityType = 'team';
    entity.entityId = 'team-1';
    entity.pricing = {
      acceptingApplications: true,
      mainSlot: {
        tier: 'main',
        price: { amount: 10, currency: 'JPY' },
        benefits: [],
        available: true,
        maxSlots: 1,
      },
    };

    try {
      mapper.toDomain(entity);
      throw new Error('expected-to-throw');
    } catch (error) {
      expect(error).toBeInstanceOf(TypeOrmPersistenceSchemaError);
      expect(error).toMatchObject({
        entityName: 'SponsorshipPricing',
        reason: 'invalid_enum_value',
      });
    }
  });
});

describe('SponsorshipRequestOrmMapper', () => {
  it('toDomain uses rehydrate semantics', () => {
    const mapper = new SponsorshipRequestOrmMapper(new MoneyOrmMapper());

    const entity = new SponsorshipRequestOrmEntity();
    entity.id = '00000000-0000-4000-8000-000000000001';
    entity.sponsorId = '00000000-0000-4000-8000-000000000002';
    entity.entityType = 'team';
    entity.entityId = 'team-1';
    entity.tier = 'main';
    entity.offeredAmount = { amount: 10, currency: 'USD' };
    entity.message = null;
    entity.status = 'pending';
    entity.createdAt = new Date('2025-01-01T00:00:00.000Z');
    entity.respondedAt = null;
    entity.respondedBy = null;
    entity.rejectionReason = null;

    const rehydrateSpy = vi.spyOn(SponsorshipRequest, 'rehydrate');

    const domain = mapper.toDomain(entity);

    expect(domain.id).toBe(entity.id);
    expect(rehydrateSpy).toHaveBeenCalled();
  });
});

describe('SeasonSponsorshipOrmMapper', () => {
  it('toDomain uses rehydrate semantics', () => {
    const mapper = new SeasonSponsorshipOrmMapper(new MoneyOrmMapper());

    const entity = new SeasonSponsorshipOrmEntity();
    entity.id = '00000000-0000-4000-8000-000000000001';
    entity.seasonId = '00000000-0000-4000-8000-000000000002';
    entity.leagueId = null;
    entity.sponsorId = '00000000-0000-4000-8000-000000000003';
    entity.tier = 'main';
    entity.pricing = { amount: 100, currency: 'USD' };
    entity.status = 'pending';
    entity.createdAt = new Date('2025-01-01T00:00:00.000Z');
    entity.activatedAt = null;
    entity.endedAt = null;
    entity.cancelledAt = null;
    entity.description = null;

    const rehydrateSpy = vi.spyOn(SeasonSponsorship, 'rehydrate');

    const domain = mapper.toDomain(entity);

    expect(domain.id).toBe(entity.id);
    expect(rehydrateSpy).toHaveBeenCalled();
  });
});