// ABOUTME: Unit tests for PreRaceScheduler
// ABOUTME: Tests race detail fetching and time window logic

import { Race } from '@prisma/client';
import { PreRaceScheduler } from '../pre-race-scheduler';
import { ScheduleType } from '../types';
import { TabApiClient } from '../../api/tab';
import { RaceService } from '../../services/race-service';
import { DateTime } from 'luxon';

// Mock dependencies
jest.mock('../../utils/logger', () => ({
  __esModule: true,
  default: {
    info: jest.fn(),
    debug: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
  },
}));

jest.mock('prom-client', () => ({
  Counter: jest.fn().mockImplementation(() => ({
    inc: jest.fn(),
    labels: jest.fn().mockReturnThis(),
  })),
  Histogram: jest.fn().mockImplementation(() => ({
    observe: jest.fn(),
    labels: jest.fn().mockReturnThis(),
  })),
  Gauge: jest.fn().mockImplementation(() => ({
    set: jest.fn(),
    labels: jest.fn().mockReturnThis(),
  })),
}));

jest.mock('@opentelemetry/api', () => ({
  trace: {
    getTracer: jest.fn(() => ({
      startSpan: jest.fn(() => ({
        setAttributes: jest.fn(),
        setAttribute: jest.fn(),
        setStatus: jest.fn(),
        end: jest.fn(),
      })),
    })),
  },
  SpanStatusCode: {
    OK: 1,
    ERROR: 2,
  },
}));

jest.mock('node-cron', () => ({
  schedule: jest.fn(() => ({
    stop: jest.fn(),
  })),
}));

// Mock RaceService
jest.mock('../../services/race-service');

describe('PreRaceScheduler', () => {
  let scheduler: PreRaceScheduler;
  let mockPrisma: any;
  let mockApiClient: jest.Mocked<TabApiClient>;
  let mockRaceService: jest.Mocked<RaceService>;
  let mockFindMany: jest.Mock;

  const mockSuccessResult = {
    success: true,
    raceId: 'race-1',
    runnersProcessed: 5,
    oddsSnapshotsCaptured: 5,
    resultsProcessed: 0,
    dividendsProcessed: 0,
    errors: [],
    durationMs: 100,
  };

  beforeEach(() => {
    jest.clearAllMocks();

    mockFindMany = jest.fn();

    // Create mock Prisma client
    mockPrisma = {
      race: {
        findMany: mockFindMany,
      },
      jobRun: {
        create: jest.fn().mockResolvedValue({ id: 'test-job-run-id' }),
        update: jest.fn().mockResolvedValue({}),
      },
    };

    // Create mock API client
    mockApiClient = {} as jest.Mocked<TabApiClient>;

    // Get the mocked RaceService constructor
    const MockedRaceService = RaceService as jest.MockedClass<typeof RaceService>;
    MockedRaceService.mockClear();

    // Create scheduler
    scheduler = new PreRaceScheduler(
      mockApiClient,
      mockPrisma,
      ScheduleType.PRE_RACE_T60
    );

    // Get the mock instance
    mockRaceService = MockedRaceService.mock.instances[0] as jest.Mocked<RaceService>;
    mockRaceService.fetchAndStoreRaceDetails = jest.fn().mockResolvedValue(mockSuccessResult);
  });

  describe('race detail fetching', () => {
    it('should fetch race details for each race in the time window', async () => {
      const now = DateTime.now().toUTC();
      const targetTime = now.plus({ minutes: 60 });

      const mockRaces: Partial<Race>[] = [
        { id: 'race-1', meetingId: 'meeting-A', raceNumber: 1, startTime: targetTime.toJSDate() },
        { id: 'race-2', meetingId: 'meeting-A', raceNumber: 2, startTime: targetTime.plus({ minutes: 5 }).toJSDate() },
        { id: 'race-3', meetingId: 'meeting-B', raceNumber: 1, startTime: targetTime.toJSDate() },
      ];

      mockFindMany.mockResolvedValue(mockRaces as Race[]);

      // Act
      const result = await scheduler.triggerManually();

      // Assert: fetchAndStoreRaceDetails called for each race
      expect(mockRaceService.fetchAndStoreRaceDetails).toHaveBeenCalledTimes(3);
      expect(mockRaceService.fetchAndStoreRaceDetails).toHaveBeenCalledWith('race-1', 'pre_race_t60');
      expect(mockRaceService.fetchAndStoreRaceDetails).toHaveBeenCalledWith('race-2', 'pre_race_t60');
      expect(mockRaceService.fetchAndStoreRaceDetails).toHaveBeenCalledWith('race-3', 'pre_race_t60');

      expect(result.itemsProcessed).toBe(3);
      expect(result.metadata?.racesUpdated).toBe(3);
    });

    it('should track runners and odds snapshots processed', async () => {
      const now = DateTime.now().toUTC();
      const targetTime = now.plus({ minutes: 60 });

      const mockRaces: Partial<Race>[] = [
        { id: 'race-1', meetingId: 'meeting-A', raceNumber: 1, startTime: targetTime.toJSDate() },
        { id: 'race-2', meetingId: 'meeting-A', raceNumber: 2, startTime: targetTime.toJSDate() },
      ];

      mockFindMany.mockResolvedValue(mockRaces as Race[]);

      // Act
      const result = await scheduler.triggerManually();

      // Assert: metadata includes runners and odds counts
      expect(result.metadata?.runnersProcessed).toBe(10); // 5 per race x 2 races
      expect(result.metadata?.oddsSnapshotsCaptured).toBe(10);
    });

    it('should not call fetchAndStoreRaceDetails when no races are in window', async () => {
      mockFindMany.mockResolvedValue([]);

      // Act
      const result = await scheduler.triggerManually();

      // Assert
      expect(mockRaceService.fetchAndStoreRaceDetails).not.toHaveBeenCalled();
      expect(result.itemsProcessed).toBe(0);
    });
  });

  describe('error handling', () => {
    it('should continue processing other races when one fails', async () => {
      const now = DateTime.now().toUTC();
      const targetTime = now.plus({ minutes: 60 });

      const mockRaces: Partial<Race>[] = [
        { id: 'race-1', meetingId: 'meeting-A', raceNumber: 1, startTime: targetTime.toJSDate() },
        { id: 'race-2', meetingId: 'meeting-B', raceNumber: 1, startTime: targetTime.toJSDate() },
        { id: 'race-3', meetingId: 'meeting-C', raceNumber: 1, startTime: targetTime.toJSDate() },
      ];

      mockFindMany.mockResolvedValue(mockRaces as Race[]);

      // race-2 fails
      mockRaceService.fetchAndStoreRaceDetails
        .mockResolvedValueOnce(mockSuccessResult) // race-1 succeeds
        .mockResolvedValueOnce({ ...mockSuccessResult, success: false, errors: ['API error'] }) // race-2 fails
        .mockResolvedValueOnce(mockSuccessResult); // race-3 succeeds

      // Act
      const result = await scheduler.triggerManually();

      // Assert: all 3 races attempted, 1 failed
      expect(mockRaceService.fetchAndStoreRaceDetails).toHaveBeenCalledTimes(3);
      expect(result.errors.length).toBe(1);
      expect(result.itemsProcessed).toBe(2); // 2 succeeded
    });
  });
});
