/**
 * PostRaceScheduler
 *
 * Fetches race results T+5 minutes after scheduled start time
 * Runs every minute to check for recently completed races
 */

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

export class PostRaceScheduler extends BaseScheduler {
  private raceService: RaceService;

  constructor(
    apiClient: TabApiClient,
    prisma: PrismaClient
  ) {
    super(ScheduleType.POST_RACE_T5, SCHEDULER_CONFIGS[ScheduleType.POST_RACE_T5], prisma);
    this.raceService = new RaceService(apiClient, prisma);
  }

  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,
        resultsProcessed: 0,
        dividendsProcessed: 0,
        provisionalResults: [] as Array<{ raceId: string; meetingId: string; raceNumber: number; status: string }>,
        confirmedResults: [] as Array<{ raceId: string; meetingId: string; raceNumber: number }>,
        retryScheduled: false,
      },
    };

    try {
      // Find races that started ~5 minutes ago
      const races = await this.findRecentlyCompletedRaces();

      logger.info({
        type: this.type,
        racesFound: races.length,
      }, `Found ${races.length} recently completed races`);

      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,
            startTime: race.startTime.toISOString(),
            currentStatus: race.status,
          }, 'Fetching race results');

          // Fetch race details from API
          const raceResult = await this.raceService.fetchAndStoreRaceDetails(
            race.id,
            'post_race'
          );

          if (raceResult.success) {
            result.itemsProcessed++;
            result.metadata!.racesUpdated++;
            result.metadata!.runnersProcessed += raceResult.runnersProcessed;
            result.metadata!.oddsSnapshotsCaptured += raceResult.oddsSnapshotsCaptured;
            result.metadata!.resultsProcessed += raceResult.resultsProcessed;
            result.metadata!.dividendsProcessed += raceResult.dividendsProcessed;

            // Check if results are confirmed (results and dividends processed)
            const isConfirmed = raceResult.resultsProcessed > 0;

            if (isConfirmed) {
              result.metadata!.confirmedResults.push({
                raceId: race.id,
                meetingId: race.meetingId,
                raceNumber: race.raceNumber,
              });

              logger.info({
                raceId: race.id,
                raceNumber: race.raceNumber,
                resultsProcessed: raceResult.resultsProcessed,
                dividendsProcessed: raceResult.dividendsProcessed,
              }, 'Confirmed race results received');
            } else {
              // Results not yet available - may need retry
              result.metadata!.provisionalResults.push({
                raceId: race.id,
                meetingId: race.meetingId,
                raceNumber: race.raceNumber,
                status: 'pending',
              });

              logger.info({
                raceId: race.id,
                raceNumber: race.raceNumber,
              }, 'Race results not yet available - may retry');
            }
          } 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,
            raceNumber: race.raceNumber,
            error: errorMessage,
          }, 'Failed to fetch race results');
        }
      }

      // Schedule retry if there are provisional results
      const retryCount = (context.data?.retryCount as number) || 0;
      const provisionalCount = result.metadata!.provisionalResults.length;

      if (provisionalCount > 0 && retryCount < RETRY_CONFIG.POST_RACE.maxRetries) {
        const retryDelayMs = RETRY_CONFIG.POST_RACE.retryDelays[retryCount];
        result.metadata!.retryScheduled = true;

        logger.info({
          provisionalResults: provisionalCount,
          retryCount: retryCount + 1,
          retryDelayMs,
          nextRetryAt: new Date(Date.now() + retryDelayMs).toISOString(),
        }, 'Scheduling retry for provisional results');

        this.scheduleRetry(retryDelayMs, context.jobRunId, retryCount + 1);
      }

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

      logger.info({
        type: this.type,
        racesUpdated: result.metadata!.racesUpdated,
        resultsProcessed: result.metadata!.resultsProcessed,
        dividendsProcessed: result.metadata!.dividendsProcessed,
        errors: result.errors.length,
        durationMs: result.durationMs,
      }, 'Post-race results 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,
      }, 'Post-race scheduler failed');

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

      return result;
    }
  }

  /**
   * Find races that started in the target time window and need results checking
   *
   * This includes:
   * 1. Races that finished 5-20 minutes ago (initial results check)
   * 2. Races with provisional results that need confirmation
   */
  private async findRecentlyCompletedRaces(): Promise<Race[]> {
    const now = DateTime.now().toUTC();
    const { minutesAfter, windowMinutes } = TIME_WINDOWS.POST_RACE_INITIAL;

    // Calculate time window for initial results (races that started 5-20 minutes ago)
    const windowStart = now.minus({ minutes: minutesAfter + windowMinutes });
    const windowEnd = now.minus({ minutes: minutesAfter });

    logger.debug({
      type: this.type,
      now: now.toISO(),
      windowStart: windowStart.toISO(),
      windowEnd: windowEnd.toISO(),
    }, 'Searching for recently completed races');

    const races = await this.prisma.race.findMany({
      where: {
        startTime: {
          gte: windowStart.toJSDate(),
          lte: windowEnd.toJSDate(),
        },
        // Only check races that haven't been confirmed yet
        // We check for both 'CLOSED' (provisional) and non-'FINAL' status
        status: {
          notIn: ['FINAL', 'ABANDONED', 'CANCELLED'],
        },
      },
      orderBy: {
        startTime: 'asc',
      },
    });

    logger.info({
      type: this.type,
      racesFound: races.length,
      windowMinutes: minutesAfter + windowMinutes,
    }, `Found ${races.length} races needing results check`);

    return races;
  }

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

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

    return this.executeJob(context);
  }
}
