/**
 * PreRaceScheduler
 *
 * Updates race information at T-60 and T-15 minutes before scheduled start
 * Runs every minute to check for upcoming races
 */

import { PrismaClient, Race } from '@prisma/client';
import { DateTime } from 'luxon';
import logger from '../utils/logger';
import { TabApiClient } from '../api/tab';
import { RaceService, ScrapeType } from '../services/race-service';
import { BaseScheduler } from './base-scheduler';
import { ScheduleType, JobContext, JobResult } from './types';
import { SCHEDULER_CONFIGS, TIME_WINDOWS } from './config';

export class PreRaceScheduler extends BaseScheduler {
  private raceService: RaceService;
  private readonly minutesBefore: number;
  private readonly windowMinutes: number;
  private readonly scrapeType: ScrapeType;

  constructor(
    apiClient: TabApiClient,
    prisma: PrismaClient,
    type: ScheduleType.PRE_RACE_T60 | ScheduleType.PRE_RACE_T15
  ) {
    super(type, SCHEDULER_CONFIGS[type], prisma);
    this.raceService = new RaceService(apiClient, prisma);

    // Set time window based on scheduler type
    const config = type === ScheduleType.PRE_RACE_T60
      ? TIME_WINDOWS.PRE_RACE_T60
      : TIME_WINDOWS.PRE_RACE_T15;

    this.minutesBefore = config.minutesBefore;
    this.windowMinutes = config.windowMinutes;
    this.scrapeType = type === ScheduleType.PRE_RACE_T60 ? 'pre_race_t60' : 'pre_race_t15';
  }

  protected async executeJob(_context: JobContext): Promise<JobResult> {
    const startTime = Date.now();
    const result: JobResult = {
      success: true,
      itemsProcessed: 0,
      durationMs: 0,
      errors: [],
      metadata: {
        racesUpdated: 0,
        runnersProcessed: 0,
        oddsSnapshotsCaptured: 0,
      },
    };

    try {
      // Find races starting in the target window
      const races = await this.findUpcomingRaces();

      logger.info({
        type: this.type,
        minutesBefore: this.minutesBefore,
        racesFound: races.length,
      }, `Found ${races.length} races in time window`);

      if (races.length === 0) {
        result.durationMs = Date.now() - startTime;
        return result;
      }

      // Fetch detailed data for each race
      for (const race of races) {
        try {
          logger.debug({
            raceId: race.id,
            meetingId: race.meetingId,
            raceNumber: race.raceNumber,
          }, 'Fetching race details');

          const raceResult = await this.raceService.fetchAndStoreRaceDetails(
            race.id,
            this.scrapeType
          );

          if (raceResult.success) {
            result.itemsProcessed++;
            result.metadata!.racesUpdated++;
            result.metadata!.runnersProcessed += raceResult.runnersProcessed;
            result.metadata!.oddsSnapshotsCaptured += raceResult.oddsSnapshotsCaptured;
          } else {
            result.success = false;
            result.errors.push(...raceResult.errors.map(e => `${race.id}: ${e}`));
          }

        } catch (error) {
          result.success = false;
          const errorMessage = error instanceof Error ? error.message : 'Unknown error';

          result.errors.push(`${race.id}: ${errorMessage}`);

          logger.error({
            raceId: race.id,
            error: errorMessage,
          }, 'Failed to fetch race details');
        }
      }

      result.durationMs = Date.now() - startTime;

      logger.info({
        type: this.type,
        racesUpdated: result.metadata!.racesUpdated,
        runnersProcessed: result.metadata!.runnersProcessed,
        oddsSnapshotsCaptured: result.metadata!.oddsSnapshotsCaptured,
        errors: result.errors.length,
        durationMs: result.durationMs,
      }, 'Pre-race update completed');

      return result;

    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';

      logger.error({
        type: this.type,
        error: errorMessage,
        stack: error instanceof Error ? error.stack : undefined,
      }, 'Pre-race scheduler failed');

      result.success = false;
      result.durationMs = Date.now() - startTime;
      result.errors.push(`Fatal error: ${errorMessage}`);

      return result;
    }
  }

  /**
   * Find races starting in the target time window
   */
  private async findUpcomingRaces(): Promise<Race[]> {
    const now = DateTime.now().toUTC();

    // Calculate time window
    const targetTime = now.plus({ minutes: this.minutesBefore });
    const windowStart = targetTime.minus({ minutes: this.windowMinutes / 2 });
    const windowEnd = targetTime.plus({ minutes: this.windowMinutes / 2 });

    logger.debug({
      type: this.type,
      now: now.toISO(),
      targetTime: targetTime.toISO(),
      windowStart: windowStart.toISO(),
      windowEnd: windowEnd.toISO(),
    }, 'Searching for races in time window');

    const races = await this.prisma.race.findMany({
      where: {
        startTime: {
          gte: windowStart.toJSDate(),
          lte: windowEnd.toJSDate(),
        },
        status: {
          not: 'CLOSED', // Don't update already closed races
        },
      },
      orderBy: {
        startTime: 'asc',
      },
    });

    return races;
  }

  /**
   * Manually trigger pre-race update for testing
   */
  async triggerManually(): Promise<JobResult> {
    logger.info({ type: this.type }, 'Manually triggering pre-race update');

    const context: JobContext = {
      type: this.type,
      triggeredAt: new Date(),
      jobRunId: 'manual-trigger',
      data: { manual: true },
    };

    return this.executeJob(context);
  }
}
