// ABOUTME: BullMQ worker for pre-race updates at T-60 and T-15 before race start
// ABOUTME: Refreshes odds, runners, and scratchings for specific races

import { Worker, Job } from 'bullmq';
import { PrismaClient } from '@prisma/client';
import { TabApiClient } from '../api/tab';
import { RaceService, ScrapeType } from '../services/race-service';
import logger from '../utils/logger';
import { moveToDeadLetter } from '../utils/dead-letter';
import { connection, QUEUE_NAMES } from './queue';

// ============================================================================
// Types
// ============================================================================

export interface PreRaceJobData {
  /** Array of race IDs to update */
  raceIds: string[];
  /** Type of pre-race scrape */
  scrapeType: 'pre_race_t60' | 'pre_race_t15';
  /** Internal retry counter */
  retryCount?: number;
}

interface PreRaceJobResult {
  success: boolean;
  racesUpdated: number;
  runnersProcessed: number;
  oddsSnapshotsCaptured: number;
  durationMs: number;
  errors: string[];
  metadata: {
    totalRacesInJob: number;
  };
}

// ============================================================================
// Worker Factory
// ============================================================================

export function createPreRaceWorker(
  apiClient: TabApiClient,
  prisma: PrismaClient
): Worker {
  const raceService = new RaceService(apiClient, prisma);

  const worker = new Worker<PreRaceJobData, PreRaceJobResult>(
    QUEUE_NAMES.PRE_RACE,
    async (job: Job<PreRaceJobData>): Promise<PreRaceJobResult> => {
      const startTime = Date.now();
      const { raceIds, scrapeType, retryCount = 0 } = job.data;

      logger.info(
        {
          jobId: job.id,
          raceCount: raceIds.length,
          scrapeType,
          retryCount,
        },
        'Pre-race worker: job started'
      );

      const result: PreRaceJobResult = {
        success: true,
        racesUpdated: 0,
        runnersProcessed: 0,
        oddsSnapshotsCaptured: 0,
        durationMs: 0,
        errors: [],
        metadata: {
          totalRacesInJob: raceIds.length,
        },
      };

      try {
        for (const raceId of raceIds) {
          try {
            logger.debug(
              { raceId, scrapeType },
              'Pre-race worker: fetching race details'
            );

            const raceResult = await raceService.fetchAndStoreRaceDetails(
              raceId,
              scrapeType as ScrapeType
            );

            if (raceResult.success) {
              result.racesUpdated++;
              result.runnersProcessed += raceResult.runnersProcessed;
              result.oddsSnapshotsCaptured +=
                raceResult.oddsSnapshotsCaptured;
            } else {
              result.success = false;
              result.errors.push(
                ...raceResult.errors.map((e) => `${raceId}: ${e}`)
              );
            }
          } catch (error) {
            result.success = false;
            const errorMessage =
              error instanceof Error ? error.message : 'Unknown error';
            result.errors.push(`${raceId}: ${errorMessage}`);

            logger.error(
              { raceId, scrapeType, error: errorMessage },
              'Pre-race worker: failed to fetch race details'
            );
          }
        }

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

        // Create Scrape records per race
        await createScrapeRecords(prisma, job, result);

        logger.info(
          {
            jobId: job.id,
            scrapeType,
            racesUpdated: result.racesUpdated,
            runnersProcessed: result.runnersProcessed,
            oddsSnapshotsCaptured: result.oddsSnapshotsCaptured,
            errors: result.errors.length,
            durationMs: result.durationMs,
          },
          'Pre-race worker: job completed'
        );

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

        logger.error(
          { jobId: job.id, error: errorMessage },
          'Pre-race worker: job failed'
        );

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

        throw error;
      }
    },
    {
      connection,
      concurrency: 5, // Allow concurrent pre-race updates for different races
      lockDuration: 60_000, // 1 minute lock
    }
  );

  worker.on('completed', (job: Job) => {
    logger.info(
      { jobId: job.id, queue: QUEUE_NAMES.PRE_RACE },
      'Pre-race worker: job completed event'
    );
  });

  worker.on('failed', async (job: Job | undefined, error: Error) => {
    logger.error(
      {
        jobId: job?.id,
        queue: QUEUE_NAMES.PRE_RACE,
        error: error.message,
        attemptsMade: job?.attemptsMade,
      },
      'Pre-race worker: job failed event'
    );

    // Move to dead letter queue after exhausting all retries
    if (job && job.attemptsMade >= (job.opts.attempts || 2)) {
      await moveToDeadLetter(job, error);
    }
  });

  logger.info('Pre-race worker created');
  return worker;
}

// ============================================================================
// Helpers
// ============================================================================

/**
 * Create Scrape records for each processed race
 */
async function createScrapeRecords(
  prisma: PrismaClient,
  job: Job<PreRaceJobData>,
  result: PreRaceJobResult
): Promise<void> {
  const { raceIds, scrapeType } = job.data;

  for (const raceId of raceIds) {
    try {
      await prisma.scrape.create({
        data: {
          raceId,
          scrapeType,
          success: true,
          changesDetected: {
            runnersProcessed: result.runnersProcessed,
            oddsSnapshotsCaptured: result.oddsSnapshotsCaptured,
          },
          jobRunId: job.id,
        },
      });
    } catch (error) {
      logger.error(
        {
          raceId,
          error: error instanceof Error ? error.message : 'Unknown error',
        },
        'Pre-race worker: failed to create scrape record'
      );
    }
  }
}
