// ABOUTME: BullMQ worker for post-race results collection (T+5 minutes)
// ABOUTME: Fetches final results, finishing positions, and dividends

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

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

export interface PostRaceJobData {
  /** Array of race IDs to fetch results for */
  raceIds: string[];
  /** Internal retry counter */
  retryCount?: number;
}

interface PostRaceJobResult {
  success: boolean;
  racesUpdated: number;
  resultsProcessed: number;
  dividendsProcessed: number;
  runnersProcessed: number;
  oddsSnapshotsCaptured: number;
  durationMs: number;
  errors: string[];
  metadata: {
    totalRacesInJob: number;
    confirmedResults: Array<{ raceId: string }>;
    provisionalResults: Array<{ raceId: string }>;
  };
}

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

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

  const worker = new Worker<PostRaceJobData, PostRaceJobResult>(
    QUEUE_NAMES.POST_RACE,
    async (job: Job<PostRaceJobData>): Promise<PostRaceJobResult> => {
      const startTime = Date.now();
      const { raceIds, retryCount = 0 } = job.data;

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

      const result: PostRaceJobResult = {
        success: true,
        racesUpdated: 0,
        resultsProcessed: 0,
        dividendsProcessed: 0,
        runnersProcessed: 0,
        oddsSnapshotsCaptured: 0,
        durationMs: 0,
        errors: [],
        metadata: {
          totalRacesInJob: raceIds.length,
          confirmedResults: [],
          provisionalResults: [],
        },
      };

      try {
        for (const raceId of raceIds) {
          try {
            logger.debug(
              { raceId, retryCount },
              'Post-race worker: fetching race results'
            );

            const raceResult = await raceService.fetchAndStoreRaceDetails(
              raceId,
              'post_race'
            );

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

              if (raceResult.resultsProcessed > 0) {
                result.metadata.confirmedResults.push({ raceId });
              } else {
                result.metadata.provisionalResults.push({ raceId });
              }
            } else {
              result.success = false;
              result.errors.push(
                ...raceResult.errors.map((e) => `${raceId}: ${e}`)
              );
              result.metadata.provisionalResults.push({ raceId });
            }
          } catch (error) {
            result.success = false;
            const errorMessage =
              error instanceof Error ? error.message : 'Unknown error';
            result.errors.push(`${raceId}: ${errorMessage}`);

            logger.error(
              { raceId, error: errorMessage },
              'Post-race worker: failed to fetch race results'
            );

            result.metadata.provisionalResults.push({ raceId });
          }
        }

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

        // Create Scrape records for tracking
        await createScrapeRecords(prisma, job, result);

        logger.info(
          {
            jobId: job.id,
            racesUpdated: result.racesUpdated,
            resultsProcessed: result.resultsProcessed,
            dividendsProcessed: result.dividendsProcessed,
            confirmedResults: result.metadata.confirmedResults.length,
            provisionalResults: result.metadata.provisionalResults.length,
            errors: result.errors.length,
            durationMs: result.durationMs,
          },
          'Post-race worker: job completed'
        );

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

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

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

        throw error;
      }
    },
    {
      connection,
      concurrency: 5,
      lockDuration: 60_000, // 1 minute lock
    }
  );

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

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

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

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

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

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

  for (const raceId of raceIds) {
    try {
      const isConfirmed = result.metadata.confirmedResults.some(
        (r) => r.raceId === raceId
      );

      await prisma.scrape.create({
        data: {
          raceId,
          scrapeType: 'post_race',
          success: isConfirmed || !result.metadata.provisionalResults.some(
            (r) => r.raceId === raceId
          ),
          changesDetected: {
            resultsProcessed: result.resultsProcessed,
            dividendsProcessed: result.dividendsProcessed,
            confirmed: isConfirmed,
          },
          jobRunId: job.id,
        },
      });
    } catch (error) {
      logger.error(
        {
          raceId,
          error: error instanceof Error ? error.message : 'Unknown error',
        },
        'Post-race worker: failed to create scrape record'
      );
    }
  }
}
