import { Game } from '@core/racing/domain/entities/Game';
import { LeagueWallet } from '@core/racing/domain/entities/league-wallet/LeagueWallet';
import { Transaction, type TransactionStatus, type TransactionType } 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, type SponsorableEntityType, type SponsorshipRequestStatus } from '@core/racing/domain/entities/SponsorshipRequest';
import { SponsorshipPricing } from '@core/racing/domain/value-objects/SponsorshipPricing';

import {
  GameOrmEntity,
  LeagueWalletOrmEntity,
  SeasonSponsorshipOrmEntity,
  SponsorOrmEntity,
  SponsorshipPricingOrmEntity,
  SponsorshipRequestOrmEntity,
  TransactionOrmEntity,
} from '../entities/MissingRacingOrmEntities';
import {
  assertArray,
  assertBoolean,
  assertDate,
  assertEnumValue,
  assertNonEmptyString,
  assertOptionalStringOrNull,
  assertRecord,
} from '../schema/TypeOrmSchemaGuards';
import { TypeOrmPersistenceSchemaError } from '../errors/TypeOrmPersistenceSchemaError';
import { MoneyOrmMapper } from './MoneyOrmMapper';

const VALID_CURRENCIES = ['USD', 'EUR', 'GBP'] as const;

const VALID_SPONSORABLE_ENTITY_TYPES = ['driver', 'team', 'race', 'season'] as const;

const VALID_SPONSORSHIP_REQUEST_STATUSES = ['pending', 'accepted', 'rejected', 'withdrawn'] as const;

const VALID_SPONSORSHIP_TIERS = ['main', 'secondary'] as const;

const VALID_SPONSORSHIP_STATUSES = ['pending', 'active', 'ended', 'cancelled'] as const;

const VALID_TRANSACTION_TYPES = [
  'sponsorship_payment',
  'membership_payment',
  'prize_payout',
  'withdrawal',
  'refund',
] as const;

const VALID_TRANSACTION_STATUSES = ['pending', 'completed', 'failed', 'cancelled'] as const;

type SerializedSponsorshipSlotConfig = {
  tier: 'main' | 'secondary';
  price: { amount: number; currency: (typeof VALID_CURRENCIES)[number] };
  benefits: string[];
  available: boolean;
  maxSlots: number;
};

type SerializedSponsorshipPricing = {
  mainSlot?: SerializedSponsorshipSlotConfig;
  secondarySlots?: SerializedSponsorshipSlotConfig;
  acceptingApplications: boolean;
  customRequirements?: string;
};

function assertSerializedSlotConfig(value: unknown): asserts value is SerializedSponsorshipSlotConfig {
  const entityName = 'SponsorshipPricing';
  const fieldName = 'pricing.slot';

  assertRecord(entityName, fieldName, value);

  assertEnumValue(entityName, `${fieldName}.tier`, value.tier, VALID_SPONSORSHIP_TIERS);

  assertRecord(entityName, `${fieldName}.price`, value.price);
  const amount = (value.price as Record<string, unknown>).amount;
  const currency = (value.price as Record<string, unknown>).currency;

  if (typeof amount !== 'number' || Number.isNaN(amount)) {
    throw new TypeOrmPersistenceSchemaError({ entityName, fieldName: `${fieldName}.price.amount`, reason: 'not_number' });
  }
  if (typeof currency !== 'string') {
    throw new TypeOrmPersistenceSchemaError({ entityName, fieldName: `${fieldName}.price.currency`, reason: 'not_string' });
  }
  if (!(VALID_CURRENCIES as readonly string[]).includes(currency)) {
    throw new TypeOrmPersistenceSchemaError({
      entityName,
      fieldName: `${fieldName}.price.currency`,
      reason: 'invalid_enum_value',
    });
  }

  assertArray(entityName, `${fieldName}.benefits`, value.benefits);
  for (const benefit of value.benefits) {
    if (typeof benefit !== 'string') {
      throw new TypeOrmPersistenceSchemaError({
        entityName,
        fieldName: `${fieldName}.benefits`,
        reason: 'not_string',
      });
    }
  }

  assertBoolean(entityName, `${fieldName}.available`, value.available);

  if (typeof value.maxSlots !== 'number' || !Number.isInteger(value.maxSlots)) {
    throw new TypeOrmPersistenceSchemaError({
      entityName,
      fieldName: `${fieldName}.maxSlots`,
      reason: 'not_integer',
    });
  }
}

function assertSerializedSponsorshipPricing(value: unknown): asserts value is SerializedSponsorshipPricing {
  const entityName = 'SponsorshipPricing';
  const fieldName = 'pricing';

  assertRecord(entityName, fieldName, value);

  assertBoolean(entityName, `${fieldName}.acceptingApplications`, value.acceptingApplications);

  if (value.customRequirements !== undefined && typeof value.customRequirements !== 'string') {
    throw new TypeOrmPersistenceSchemaError({
      entityName,
      fieldName: `${fieldName}.customRequirements`,
      reason: 'not_string',
    });
  }

  if (value.mainSlot !== undefined) {
    assertSerializedSlotConfig(value.mainSlot);
  }
  if (value.secondarySlots !== undefined) {
    assertSerializedSlotConfig(value.secondarySlots);
  }
}

export class GameOrmMapper {
  toOrmEntity(domain: Game): GameOrmEntity {
    const entity = new GameOrmEntity();
    entity.id = domain.id.toString();
    entity.name = domain.name.toString();
    return entity;
  }

  toDomain(entity: GameOrmEntity): Game {
    const entityName = 'Game';
    assertNonEmptyString(entityName, 'id', entity.id);
    assertNonEmptyString(entityName, 'name', entity.name);

    try {
      return Game.rehydrate({ id: entity.id, name: entity.name });
    } catch {
      throw new TypeOrmPersistenceSchemaError({ entityName, fieldName: '__root', reason: 'invalid_shape' });
    }
  }
}

export class SponsorOrmMapper {
  toOrmEntity(domain: Sponsor): SponsorOrmEntity {
    const entity = new SponsorOrmEntity();
    entity.id = domain.id.toString();
    entity.name = domain.name.toString();
    entity.contactEmail = domain.contactEmail.toString();
    entity.logoUrl = domain.logoUrl?.toString() ?? null;
    entity.websiteUrl = domain.websiteUrl?.toString() ?? null;
    entity.createdAt = domain.createdAt.toDate();
    return entity;
  }

  toDomain(entity: SponsorOrmEntity): Sponsor {
    const entityName = 'Sponsor';

    assertNonEmptyString(entityName, 'id', entity.id);
    assertNonEmptyString(entityName, 'name', entity.name);
    assertNonEmptyString(entityName, 'contactEmail', entity.contactEmail);
    assertOptionalStringOrNull(entityName, 'logoUrl', entity.logoUrl);
    assertOptionalStringOrNull(entityName, 'websiteUrl', entity.websiteUrl);
    assertDate(entityName, 'createdAt', entity.createdAt);

    try {
      return Sponsor.rehydrate({
        id: entity.id,
        name: entity.name,
        contactEmail: entity.contactEmail,
        ...(entity.logoUrl !== null && entity.logoUrl !== undefined ? { logoUrl: entity.logoUrl } : {}),
        ...(entity.websiteUrl !== null && entity.websiteUrl !== undefined ? { websiteUrl: entity.websiteUrl } : {}),
        createdAt: entity.createdAt,
      });
    } catch {
      throw new TypeOrmPersistenceSchemaError({ entityName, fieldName: '__root', reason: 'invalid_shape' });
    }
  }
}

export class LeagueWalletOrmMapper {
  constructor(
    private readonly moneyMapper: MoneyOrmMapper,
  ) {}

  toOrmEntity(domain: LeagueWallet): LeagueWalletOrmEntity {
    const entity = new LeagueWalletOrmEntity();
    entity.id = domain.id.toString();
    entity.leagueId = domain.leagueId.toString();
    entity.balance = this.moneyMapper.toOrm(domain.balance);
    entity.transactionIds = domain.transactionIds.map((t) => t.toString());
    entity.createdAt = domain.createdAt;
    return entity;
  }

  toDomain(entity: LeagueWalletOrmEntity): LeagueWallet {
    const entityName = 'LeagueWallet';

    assertNonEmptyString(entityName, 'id', entity.id);
    assertNonEmptyString(entityName, 'leagueId', entity.leagueId);
    assertArray(entityName, 'transactionIds', entity.transactionIds);
    for (const tid of entity.transactionIds) {
      assertNonEmptyString(entityName, 'transactionIds', tid);
    }
    assertRecord(entityName, 'balance', entity.balance);
    assertDate(entityName, 'createdAt', entity.createdAt);

    const balance = this.moneyMapper.toDomain(entityName, 'balance', entity.balance);

    try {
      return LeagueWallet.rehydrate({
        id: entity.id,
        leagueId: entity.leagueId,
        balance,
        transactionIds: entity.transactionIds,
        createdAt: entity.createdAt,
      });
    } catch {
      throw new TypeOrmPersistenceSchemaError({ entityName, fieldName: '__root', reason: 'invalid_shape' });
    }
  }
}

export class TransactionOrmMapper {
  constructor(
    private readonly moneyMapper: MoneyOrmMapper,
  ) {}

  toOrmEntity(domain: Transaction): TransactionOrmEntity {
    const entity = new TransactionOrmEntity();
    entity.id = domain.id.toString();
    entity.walletId = domain.walletId.toString();
    entity.type = domain.type;
    entity.amount = this.moneyMapper.toOrm(domain.amount);
    entity.platformFee = this.moneyMapper.toOrm(domain.platformFee);
    entity.netAmount = this.moneyMapper.toOrm(domain.netAmount);
    entity.status = domain.status;
    entity.createdAt = domain.createdAt;
    entity.completedAt = domain.completedAt ?? null;
    entity.description = domain.description ?? null;
    entity.metadata = domain.metadata ?? null;
    return entity;
  }

  toDomain(entity: TransactionOrmEntity): Transaction {
    const entityName = 'Transaction';

    assertNonEmptyString(entityName, 'id', entity.id);
    assertNonEmptyString(entityName, 'walletId', entity.walletId);
    assertEnumValue(entityName, 'type', entity.type, VALID_TRANSACTION_TYPES);
    assertEnumValue(entityName, 'status', entity.status, VALID_TRANSACTION_STATUSES);
    assertRecord(entityName, 'amount', entity.amount);
    assertRecord(entityName, 'platformFee', entity.platformFee);
    assertRecord(entityName, 'netAmount', entity.netAmount);
    assertDate(entityName, 'createdAt', entity.createdAt);

    const amount = this.moneyMapper.toDomain(entityName, 'amount', entity.amount);
    const platformFee = this.moneyMapper.toDomain(entityName, 'platformFee', entity.platformFee);
    const netAmount = this.moneyMapper.toDomain(entityName, 'netAmount', entity.netAmount);

    if (entity.completedAt !== null && entity.completedAt !== undefined && !(entity.completedAt instanceof Date)) {
      throw new TypeOrmPersistenceSchemaError({ entityName, fieldName: 'completedAt', reason: 'not_date' });
    }

    assertOptionalStringOrNull(entityName, 'description', entity.description);

    if (entity.metadata !== null && entity.metadata !== undefined) {
      assertRecord(entityName, 'metadata', entity.metadata);
    }

    try {
      return Transaction.rehydrate({
        id: entity.id,
        walletId: entity.walletId,
        type: entity.type as TransactionType,
        amount,
        platformFee,
        netAmount,
        status: entity.status as TransactionStatus,
        createdAt: entity.createdAt,
        ...(entity.completedAt !== null && entity.completedAt !== undefined ? { completedAt: entity.completedAt } : {}),
        ...(entity.description !== null && entity.description !== undefined ? { description: entity.description } : {}),
        ...(entity.metadata !== null && entity.metadata !== undefined ? { metadata: entity.metadata } : {}),
      });
    } catch {
      throw new TypeOrmPersistenceSchemaError({ entityName, fieldName: '__root', reason: 'invalid_shape' });
    }
  }
}

export class SponsorshipPricingOrmMapper {
  constructor(
    private readonly moneyMapper: MoneyOrmMapper,
  ) {}

  makeId(entityType: SponsorableEntityType, entityId: string): string {
    return `${entityType}:${entityId}`;
  }

  toOrmEntity(entityType: SponsorableEntityType, entityId: string, pricing: SponsorshipPricing): SponsorshipPricingOrmEntity {
    const entity = new SponsorshipPricingOrmEntity();
    entity.id = this.makeId(entityType, entityId);
    entity.entityType = entityType;
    entity.entityId = entityId;

    const serializeSlot = (slot: SponsorshipPricing['mainSlot']): SerializedSponsorshipSlotConfig | undefined => {
      if (!slot) return undefined;
      return {
        tier: slot.tier,
        price: this.moneyMapper.toOrm(slot.price),
        benefits: slot.benefits,
        available: slot.available,
        maxSlots: slot.maxSlots,
      };
    };

    entity.pricing = {
      acceptingApplications: pricing.acceptingApplications,
      ...(pricing.customRequirements !== undefined ? { customRequirements: pricing.customRequirements } : {}),
      ...(pricing.mainSlot !== undefined ? { mainSlot: serializeSlot(pricing.mainSlot)! } : {}),
      ...(pricing.secondarySlots !== undefined ? { secondarySlots: serializeSlot(pricing.secondarySlots)! } : {}),
    } satisfies SerializedSponsorshipPricing;

    return entity;
  }

  toDomain(entity: SponsorshipPricingOrmEntity): SponsorshipPricing {
    const entityName = 'SponsorshipPricing';

    assertNonEmptyString(entityName, 'id', entity.id);
    assertEnumValue(entityName, 'entityType', entity.entityType, VALID_SPONSORABLE_ENTITY_TYPES);
    assertNonEmptyString(entityName, 'entityId', entity.entityId);

    assertSerializedSponsorshipPricing(entity.pricing);

    const parsed = entity.pricing as SerializedSponsorshipPricing;

    const parseSlot = (slot: SerializedSponsorshipSlotConfig | undefined): SponsorshipPricing['mainSlot'] => {
      if (!slot) return undefined;
      return {
        tier: slot.tier,
        price: this.moneyMapper.toDomain('SponsorshipPricing', 'pricing.slot.price', slot.price),
        benefits: slot.benefits,
        available: slot.available,
        maxSlots: slot.maxSlots,
      };
    };

    try {
      return SponsorshipPricing.create({
        acceptingApplications: parsed.acceptingApplications,
        ...(parsed.customRequirements !== undefined ? { customRequirements: parsed.customRequirements } : {}),
        ...(parsed.mainSlot !== undefined ? { mainSlot: parseSlot(parsed.mainSlot) } : {}),
        ...(parsed.secondarySlots !== undefined ? { secondarySlots: parseSlot(parsed.secondarySlots) } : {}),
      });
    } catch {
      throw new TypeOrmPersistenceSchemaError({ entityName, fieldName: '__root', reason: 'invalid_shape' });
    }
  }
}

export class SponsorshipRequestOrmMapper {
  constructor(
    private readonly moneyMapper: MoneyOrmMapper,
  ) {}

  toOrmEntity(domain: SponsorshipRequest): SponsorshipRequestOrmEntity {
    const entity = new SponsorshipRequestOrmEntity();
    entity.id = domain.id;
    entity.sponsorId = domain.sponsorId;
    entity.entityType = domain.entityType;
    entity.entityId = domain.entityId;
    entity.tier = domain.tier;
    entity.offeredAmount = this.moneyMapper.toOrm(domain.offeredAmount);
    entity.message = domain.message ?? null;
    entity.status = domain.status;
    entity.createdAt = domain.createdAt;
    entity.respondedAt = domain.respondedAt ?? null;
    entity.respondedBy = domain.respondedBy ?? null;
    entity.rejectionReason = domain.rejectionReason ?? null;
    return entity;
  }

  toDomain(entity: SponsorshipRequestOrmEntity): SponsorshipRequest {
    const entityName = 'SponsorshipRequest';

    assertNonEmptyString(entityName, 'id', entity.id);
    assertNonEmptyString(entityName, 'sponsorId', entity.sponsorId);
    assertEnumValue(entityName, 'entityType', entity.entityType, VALID_SPONSORABLE_ENTITY_TYPES);
    assertNonEmptyString(entityName, 'entityId', entity.entityId);
    assertEnumValue(entityName, 'tier', entity.tier, VALID_SPONSORSHIP_TIERS);
    assertRecord(entityName, 'offeredAmount', entity.offeredAmount);
    assertEnumValue(entityName, 'status', entity.status, VALID_SPONSORSHIP_REQUEST_STATUSES);
    assertOptionalStringOrNull(entityName, 'message', entity.message);
    assertOptionalStringOrNull(entityName, 'rejectionReason', entity.rejectionReason);
    assertDate(entityName, 'createdAt', entity.createdAt);

    const offeredAmount = this.moneyMapper.toDomain(entityName, 'offeredAmount', entity.offeredAmount);

    if (entity.respondedAt !== null && entity.respondedAt !== undefined && !(entity.respondedAt instanceof Date)) {
      throw new TypeOrmPersistenceSchemaError({ entityName, fieldName: 'respondedAt', reason: 'not_date' });
    }
    if (entity.respondedBy !== null && entity.respondedBy !== undefined) {
      assertNonEmptyString(entityName, 'respondedBy', entity.respondedBy);
    }

    try {
      return SponsorshipRequest.rehydrate({
        id: entity.id,
        sponsorId: entity.sponsorId,
        entityType: entity.entityType as SponsorableEntityType,
        entityId: entity.entityId,
        tier: entity.tier as any,
        offeredAmount,
        ...(entity.message !== null && entity.message !== undefined ? { message: entity.message } : {}),
        status: entity.status as SponsorshipRequestStatus,
        createdAt: entity.createdAt,
        ...(entity.respondedAt !== null && entity.respondedAt !== undefined ? { respondedAt: entity.respondedAt } : {}),
        ...(entity.respondedBy !== null && entity.respondedBy !== undefined ? { respondedBy: entity.respondedBy } : {}),
        ...(entity.rejectionReason !== null && entity.rejectionReason !== undefined ? { rejectionReason: entity.rejectionReason } : {}),
      });
    } catch {
      throw new TypeOrmPersistenceSchemaError({ entityName, fieldName: '__root', reason: 'invalid_shape' });
    }
  }
}

export class SeasonSponsorshipOrmMapper {
  constructor(
    private readonly moneyMapper: MoneyOrmMapper,
  ) {}

  toOrmEntity(domain: SeasonSponsorship): SeasonSponsorshipOrmEntity {
    const entity = new SeasonSponsorshipOrmEntity();
    entity.id = domain.id;
    entity.seasonId = domain.seasonId;
    entity.leagueId = domain.leagueId ?? null;
    entity.sponsorId = domain.sponsorId;
    entity.tier = domain.tier;
    entity.pricing = this.moneyMapper.toOrm(domain.pricing);
    entity.status = domain.status;
    entity.createdAt = domain.createdAt;
    entity.activatedAt = domain.activatedAt ?? null;
    entity.endedAt = domain.endedAt ?? null;
    entity.cancelledAt = domain.cancelledAt ?? null;
    entity.description = domain.description ?? null;
    return entity;
  }

  toDomain(entity: SeasonSponsorshipOrmEntity): SeasonSponsorship {
    const entityName = 'SeasonSponsorship';

    assertNonEmptyString(entityName, 'id', entity.id);
    assertNonEmptyString(entityName, 'seasonId', entity.seasonId);
    assertOptionalStringOrNull(entityName, 'leagueId', entity.leagueId);
    assertNonEmptyString(entityName, 'sponsorId', entity.sponsorId);
    assertEnumValue(entityName, 'tier', entity.tier, VALID_SPONSORSHIP_TIERS);
    assertRecord(entityName, 'pricing', entity.pricing);
    assertEnumValue(entityName, 'status', entity.status, VALID_SPONSORSHIP_STATUSES);
    assertOptionalStringOrNull(entityName, 'description', entity.description);
    assertDate(entityName, 'createdAt', entity.createdAt);

    const pricing = this.moneyMapper.toDomain(entityName, 'pricing', entity.pricing);

    const dateOrNull = (fieldName: string, d: Date | null) => {
      if (d === null) return undefined;
      if (!(d instanceof Date)) {
        throw new TypeOrmPersistenceSchemaError({ entityName, fieldName, reason: 'not_date' });
      }
      return d;
    };

    try {
      return SeasonSponsorship.rehydrate({
        id: entity.id,
        seasonId: entity.seasonId,
        ...(entity.leagueId !== null && entity.leagueId !== undefined ? { leagueId: entity.leagueId } : {}),
        sponsorId: entity.sponsorId,
        tier: entity.tier as any,
        pricing,
        status: entity.status as any,
        createdAt: entity.createdAt,
        ...(dateOrNull('activatedAt', entity.activatedAt) ? { activatedAt: entity.activatedAt! } : {}),
        ...(dateOrNull('endedAt', entity.endedAt) ? { endedAt: entity.endedAt! } : {}),
        ...(dateOrNull('cancelledAt', entity.cancelledAt) ? { cancelledAt: entity.cancelledAt! } : {}),
        ...(entity.description !== null && entity.description !== undefined ? { description: entity.description } : {}),
      });
    } catch {
      throw new TypeOrmPersistenceSchemaError({ entityName, fieldName: '__root', reason: 'invalid_shape' });
    }
  }
}