import type { League } from '@core/racing/domain/entities/League';
import { SponsorshipRequest } from '@core/racing/domain/entities/SponsorshipRequest';
import { Season } from '@core/racing/domain/entities/season/Season';
import { SeasonSponsorship } from '@core/racing/domain/entities/season/SeasonSponsorship';
import type { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
import { Money } from '@core/racing/domain/value-objects/Money';
import type { SeasonStatusValue } from '@core/racing/domain/value-objects/SeasonStatus';
import { faker } from '@faker-js/faker';
import { seedId } from './SeedIdHelper';

export class RacingSeasonSponsorshipFactory {
  constructor(
    private readonly baseDate: Date,
    private readonly persistence: 'postgres' | 'inmemory' = 'inmemory',
  ) {}

  createSeasons(leagues: League[]): Season[] {
    const seasons: Season[] = [];

    for (const league of leagues) {
      const leagueId = league.id.toString();
      const leagueIndex = parseInt(leagueId.split('-')[1] || '0');

      // Create 2-4 seasons per league to reach ~100 total seasons
      const seasonCount = faker.number.int({ min: 2, max: 4 });

      for (let i = 0; i < seasonCount; i++) {
        const id = seedId(`${leagueId}-season-${i + 1}`, this.persistence);
        
        // Systematically cover all 5 statuses across all leagues
        // planned: 20%, active: 20%, completed: 30%, archived: 20%, cancelled: 10%
        let status: SeasonStatusValue;
        if (i === 0 && leagueIndex % 5 === 0) {
          status = 'planned';
        } else if (i === 0 && leagueIndex % 5 === 1) {
          status = 'active';
        } else if (i === 1 && leagueIndex % 5 === 2) {
          status = 'completed';
        } else if (i === 2 && leagueIndex % 5 === 3) {
          status = 'archived';
        } else if (i === 3 && leagueIndex % 5 === 4) {
          status = 'cancelled';
        } else {
          // Weighted random distribution
          const statusWeights = [
            { weight: 2, value: 'planned' as const },
            { weight: 2, value: 'active' as const },
            { weight: 3, value: 'completed' as const },
            { weight: 2, value: 'archived' as const },
            { weight: 1, value: 'cancelled' as const },
          ];
          const totalWeight = statusWeights.reduce((sum, item) => sum + item.weight, 0);
          const random = faker.number.int({ min: 1, max: totalWeight });
          let cumulative = 0;
          status = 'planned';
          for (const item of statusWeights) {
            cumulative += item.weight;
            if (random <= cumulative) {
              status = item.value;
              break;
            }
          }
        }

        const baseYear = this.baseDate.getUTCFullYear() + faker.number.int({ min: -1, max: 1 });

        // Calculate dates based on status
        let startDate: Date | undefined;
        let endDate: Date | undefined;
        let schedulePublished: boolean | undefined;
        let participantCount: number | undefined;
        let maxDrivers: number | undefined;

        // Special case: ensure league-5-season-1 starts unpublished for test compatibility
        const isTestSeason = id === seedId('league-5-season-1', this.persistence);

        switch (status) {
          case 'planned':
            startDate = this.daysFromBase(faker.number.int({ min: 7, max: 90 }));
            schedulePublished = isTestSeason ? false : faker.datatype.boolean({ probability: 0.6 });
            participantCount = 0;
            break;

          case 'active':
            startDate = this.daysFromBase(faker.number.int({ min: -60, max: -1 }));
            schedulePublished = true;
            maxDrivers = faker.number.int({
              min: 10,
              max: Math.max(10, Math.min(league.settings.maxDrivers || 32, 100))
            });
            participantCount = faker.number.int({ min: 5, max: maxDrivers });
            break;

          case 'completed':
            startDate = this.daysFromBase(faker.number.int({ min: -180, max: -60 }));
            endDate = this.daysFromBase(faker.number.int({ min: -59, max: -7 }));
            schedulePublished = true;
            maxDrivers = faker.number.int({
              min: 10,
              max: Math.max(10, Math.min(league.settings.maxDrivers || 32, 100))
            });
            participantCount = faker.number.int({ min: 10, max: maxDrivers });
            break;

          case 'archived':
            startDate = this.daysFromBase(faker.number.int({ min: -365, max: -200 }));
            endDate = this.daysFromBase(faker.number.int({ min: -199, max: -150 }));
            schedulePublished = true;
            maxDrivers = faker.number.int({
              min: 10,
              max: Math.max(10, Math.min(league.settings.maxDrivers || 32, 100))
            });
            participantCount = faker.number.int({ min: 8, max: maxDrivers });
            break;

          case 'cancelled':
            startDate = this.daysFromBase(faker.number.int({ min: -30, max: -1 }));
            endDate = this.daysFromBase(faker.number.int({ min: -1, max: 1 })); // Cancelled early
            schedulePublished = isTestSeason ? false : faker.datatype.boolean({ probability: 0.3 });
            // Cancelled seasons can have maxDrivers but participantCount should be low
            maxDrivers = faker.number.int({
              min: 5,
              max: Math.max(5, Math.min(league.settings.maxDrivers || 32, 100))
            });
            participantCount = faker.number.int({ min: 0, max: Math.min(5, maxDrivers) }); // Minimal participants
            break;
        }

        // Build season data with proper undefined handling
        const seasonData: {
          id: string;
          leagueId: string;
          gameId: string;
          name: string;
          year?: number;
          order?: number;
          status: SeasonStatusValue;
          startDate?: Date;
          endDate?: Date;
          schedulePublished?: boolean;
          participantCount?: number;
          maxDrivers?: number;
        } = {
          id,
          leagueId,
          gameId: 'iracing',
          name: `${faker.word.adjective()} ${faker.word.noun()} Season`,
          status,
        };

        // Add optional fields only if they have values
        if (baseYear !== undefined) seasonData.year = baseYear;
        if (i + 1 !== undefined) seasonData.order = i + 1;
        if (startDate !== undefined) seasonData.startDate = startDate;
        if (endDate !== undefined) seasonData.endDate = endDate;
        if (schedulePublished !== undefined) seasonData.schedulePublished = schedulePublished;
        if (participantCount !== undefined) seasonData.participantCount = participantCount;
        if (maxDrivers !== undefined) seasonData.maxDrivers = maxDrivers;

        const season = Season.create(seasonData);
        seasons.push(season);
      }
    }

    return seasons;
  }

  createSeasonSponsorships(seasons: Season[], sponsors: Sponsor[]): SeasonSponsorship[] {
    const sponsorships: SeasonSponsorship[] = [];
    const sponsorIds = sponsors.map((s) => s.id.toString());

    for (const season of seasons) {
      const expectedSeasonId = seedId('season-1', this.persistence);
      const sponsorshipCount =
        season.id === expectedSeasonId
          ? 2
          : season.status.isActive()
            ? faker.number.int({ min: 0, max: 2 })
            : faker.number.int({ min: 0, max: 1 });

      const usedSponsorIds = new Set<string>();

      for (let i = 0; i < sponsorshipCount; i++) {
        const tier: SeasonSponsorship['tier'] =
          i === 0 ? 'main' : faker.helpers.arrayElement(['secondary', 'secondary', 'main'] as const);

        const sponsorIdCandidate = faker.helpers.arrayElement(sponsorIds);
        const sponsorId = usedSponsorIds.has(sponsorIdCandidate)
          ? faker.helpers.arrayElement(sponsorIds)
          : sponsorIdCandidate;

        usedSponsorIds.add(sponsorId);

        const base = SeasonSponsorship.create({
          id: seedId(`season-sponsorship-${season.id}-${i + 1}`, this.persistence),
          seasonId: season.id,
          leagueId: season.leagueId,
          sponsorId,
          tier,
          pricing: Money.create(
            tier === 'main' ? faker.number.int({ min: 900, max: 2500 }) : faker.number.int({ min: 250, max: 900 }),
            'USD',
          ),
          createdAt: faker.date.recent({ days: 120, refDate: this.baseDate }),
          description: tier === 'main' ? 'Main sponsor slot' : 'Secondary sponsor slot',
          ...(season.status.isActive()
            ? {
                status: faker.helpers.arrayElement(['active', 'pending'] as const),
                activatedAt: faker.date.recent({ days: 30, refDate: this.baseDate }),
              }
            : season.status.isCompleted() || season.status.isArchived()
              ? {
                  status: faker.helpers.arrayElement(['ended', 'cancelled'] as const),
                  endedAt: faker.date.recent({ days: 200, refDate: this.baseDate }),
                }
              : {
                  status: faker.helpers.arrayElement(['pending', 'cancelled'] as const),
                }),
        });

        sponsorships.push(base);
      }
    }

    return sponsorships;
  }

  createSponsorshipRequests(seasons: Season[], sponsors: Sponsor[]): SponsorshipRequest[] {
    const requests: SponsorshipRequest[] = [];
    const sponsorIds = sponsors.map((s) => s.id.toString());

    for (const season of seasons) {
      const expectedSeasonId = seedId('season-1', this.persistence);
      const isHighTrafficDemo = season.id === expectedSeasonId;
      const maxRequests =
        isHighTrafficDemo
          ? 8
          : season.status.isActive()
            ? faker.number.int({ min: 0, max: 4 })
            : faker.number.int({ min: 0, max: 1 });

      for (let i = 0; i < maxRequests; i++) {
        const tier: SponsorshipRequest['tier'] =
          i === 0 ? 'main' : faker.helpers.arrayElement(['secondary', 'secondary', 'main'] as const);

        const sponsorId =
          isHighTrafficDemo && i === 0
            ? seedId('demo-sponsor-1', this.persistence)
            : faker.helpers.arrayElement(sponsorIds);

        const offeredAmount = Money.create(
          tier === 'main' ? faker.number.int({ min: 1000, max: 3500 }) : faker.number.int({ min: 200, max: 1200 }),
          'USD',
        );

        const includeMessage = i % 3 !== 0;
        const message = includeMessage
          ? faker.helpers.arrayElement([
              'We would love to sponsor this season — high-quality sim racing content fits our brand.',
              'We can provide prize pool support + gear giveaways for podium finishers.',
              'Interested in the main slot: branding on liveries + broadcast overlays.',
              'We want a secondary slot to test engagement before a bigger commitment.',
            ])
          : undefined;

        // A mix of statuses for edge cases (pending is what the UI lists)
        const status =
          season.status.isActive()
            ? faker.helpers.arrayElement(['pending', 'pending', 'pending', 'rejected', 'withdrawn'] as const)
            : faker.helpers.arrayElement(['pending', 'rejected'] as const);

        requests.push(
          SponsorshipRequest.create({
            id: seedId(`sponsorship-request-${season.id}-${i + 1}`, this.persistence),
            sponsorId,
            entityType: 'season',
            entityId: season.id,
            tier,
            offeredAmount,
            ...(message !== undefined ? { message } : {}),
            status,
            createdAt: faker.date.recent({ days: 60, refDate: this.baseDate }),
          }),
        );
      }
    }

    return requests;
  }

  private daysFromBase(days: number): Date {
    return new Date(this.baseDate.getTime() + days * 24 * 60 * 60 * 1000);
  }
}