/**
 * Unit tests for MeetingService
 * Following TDD approach - tests written before implementation
 */

import { MeetingService } from '../meeting-service';
import { TabApiClient } from '../../api/tab';

// Mock dependencies
jest.mock('../../api/tab');
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(),
  })),
}));
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,
  },
}));

describe('MeetingService', () => {
  let service: MeetingService;
  let mockApiClient: jest.Mocked<TabApiClient>;
  let mockPrisma: any;

  beforeEach(() => {
    // Create mock API client
    mockApiClient = {
      getMeetings: jest.fn(),
      getMeetingById: jest.fn(),
      getRateLimitStatus: jest.fn(),
    } as any;

    // Create mock Prisma client
    mockPrisma = {
      meeting: {
        upsert: jest.fn(),
        findUnique: jest.fn(),
        findMany: jest.fn(),
        update: jest.fn(),
      },
      race: {
        upsert: jest.fn(),
        deleteMany: jest.fn(),
      },
      $transaction: jest.fn((callback) => callback(mockPrisma)),
    } as any;

    service = new MeetingService(mockApiClient, mockPrisma);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('fetchAndStoreMeetings', () => {
    const mockApiMeetings = {
      header: {
        title: 'Race Meetings',
        generated_time: '2026-01-14T01:00:00Z',
        url: '/affiliates/v1/racing/meetings',
      },
      params: {
        enc: 'json',
        date_from: '2026-01-14T00:00:00Z',
        date_to: '2026-01-14T00:00:00Z',
      },
      data: {
        meetings: [
          {
            meeting: '6e2182c6-9cf7-41dc-b0e0-5af4e2bd67ef',
            name: 'Flemington',
            date: '2026-01-14T00:00:00Z',
            track_condition: 'Good',
            category: 'T' as const,
            category_name: 'Thoroughbred Horse Racing',
            country: 'AUS',
            state: 'VIC',
            races: [
              {
                id: 'race-uuid-1',
                race_number: 1,
                name: 'Race 1',
                start_time: '2026-01-14T02:30:00Z',
                tote_start_time: '15:30:00',
                track_condition: 'Good',
                distance: 1200,
                weather: 'Fine',
                country: 'AUS',
                state: 'VIC',
                status: 'Open',
              },
            ],
          },
        ],
      },
    };

    it('should fetch and store meetings successfully', async () => {
      mockApiClient.getMeetings.mockResolvedValue(mockApiMeetings);
      mockPrisma.meeting.upsert.mockResolvedValue({} as any);
      mockPrisma.race.upsert.mockResolvedValue({} as any);

      const result = await service.fetchAndStoreMeetings({
        category: 'T',
        country: 'AUS',
        date: new Date('2026-01-14'),
      });

      expect(mockApiClient.getMeetings).toHaveBeenCalledWith({
        category: 'T',
        country: 'AUS',
        dateFrom: '2026-01-14',
        dateTo: '2026-01-14',
      });

      expect(mockPrisma.meeting.upsert).toHaveBeenCalledTimes(1);
      expect(mockPrisma.race.upsert).toHaveBeenCalledTimes(1);

      expect(result).toEqual({
        meetingsProcessed: 1,
        racesProcessed: 1,
        errors: [],
      });
    });

    it('should handle API client errors gracefully', async () => {
      const apiError = new Error('API request failed');
      mockApiClient.getMeetings.mockRejectedValue(apiError);

      await expect(
        service.fetchAndStoreMeetings({
          category: 'T',
          country: 'AUS',
          date: new Date('2026-01-14'),
        })
      ).rejects.toThrow('API request failed');
    });

    it('should use transaction for atomicity', async () => {
      mockApiClient.getMeetings.mockResolvedValue(mockApiMeetings);
      mockPrisma.meeting.upsert.mockResolvedValue({} as any);
      mockPrisma.race.upsert.mockResolvedValue({} as any);

      await service.fetchAndStoreMeetings({
        category: 'T',
        country: 'AUS',
        date: new Date('2026-01-14'),
      });

      expect(mockPrisma.$transaction).toHaveBeenCalled();
    });

    it('should map API data to database schema correctly', async () => {
      mockApiClient.getMeetings.mockResolvedValue(mockApiMeetings);
      mockPrisma.meeting.upsert.mockResolvedValue({} as any);
      mockPrisma.race.upsert.mockResolvedValue({} as any);

      await service.fetchAndStoreMeetings({
        category: 'T',
        country: 'AUS',
        date: new Date('2026-01-14'),
      });

      expect(mockPrisma.meeting.upsert).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: '6e2182c6-9cf7-41dc-b0e0-5af4e2bd67ef' },
          create: expect.objectContaining({
            id: '6e2182c6-9cf7-41dc-b0e0-5af4e2bd67ef',
            name: 'Flemington',
            country: 'AUS',
            state: 'VIC',
            category: 'T',
            trackCondition: 'Good',
          }),
          update: expect.objectContaining({
            name: 'Flemington',
            trackCondition: 'Good',
          }),
        })
      );
    });

    it('should handle multiple meetings in one request', async () => {
      const multiMeetings = {
        ...mockApiMeetings,
        data: {
          meetings: [
            mockApiMeetings.data.meetings[0],
            {
              ...mockApiMeetings.data.meetings[0],
              meeting: 'another-uuid',
              name: 'Randwick',
            },
          ],
        },
      };

      mockApiClient.getMeetings.mockResolvedValue(multiMeetings);
      mockPrisma.meeting.upsert.mockResolvedValue({} as any);
      mockPrisma.race.upsert.mockResolvedValue({} as any);

      const result = await service.fetchAndStoreMeetings({
        category: 'T',
        country: 'AUS',
        date: new Date('2026-01-14'),
      });

      expect(mockPrisma.meeting.upsert).toHaveBeenCalledTimes(2);
      expect(result.meetingsProcessed).toBe(2);
    });
  });

  describe('getMeetingById', () => {
    it('should fetch meeting from database', async () => {
      const mockDbMeeting = {
        id: '6e2182c6-9cf7-41dc-b0e0-5af4e2bd67ef',
        name: 'Flemington',
        date: new Date('2026-01-14'),
        country: 'AUS',
        state: 'VIC',
        category: 'T',
        categoryName: 'Thoroughbred',
        trackCondition: 'Good',
        weather: null,
        videoChannels: null,
        quaddie: [],
        earlyQuaddie: [],
        toteMeetingNumber: null,
        toteStatus: null,
        metadata: null,
        createdAt: new Date(),
        updatedAt: new Date(),
        races: [],
      };

      mockPrisma.meeting.findUnique.mockResolvedValue(mockDbMeeting);

      const result = await service.getMeetingById('6e2182c6-9cf7-41dc-b0e0-5af4e2bd67ef');

      expect(mockPrisma.meeting.findUnique).toHaveBeenCalledWith({
        where: { id: '6e2182c6-9cf7-41dc-b0e0-5af4e2bd67ef' },
        include: { races: true },
      });

      expect(result).toEqual(mockDbMeeting);
    });

    it('should return null if meeting not found', async () => {
      mockPrisma.meeting.findUnique.mockResolvedValue(null);

      const result = await service.getMeetingById('non-existent-uuid');

      expect(result).toBeNull();
    });
  });

  describe('getMeetingsByDate', () => {
    it('should fetch meetings for a specific date', async () => {
      const mockMeetings = [
        {
          id: 'uuid-1',
          name: 'Flemington',
          date: new Date('2026-01-14'),
          country: 'AUS',
          state: 'VIC',
          category: 'T',
          categoryName: 'Thoroughbred',
          trackCondition: 'Good',
          weather: null,
          videoChannels: null,
          quaddie: [],
          earlyQuaddie: [],
          toteMeetingNumber: null,
          toteStatus: null,
          metadata: null,
          createdAt: new Date(),
          updatedAt: new Date(),
          races: [],
        },
      ];

      mockPrisma.meeting.findMany.mockResolvedValue(mockMeetings);

      const result = await service.getMeetingsByDate(new Date('2026-01-14'), {
        country: 'AUS',
        category: 'T',
      });

      expect(mockPrisma.meeting.findMany).toHaveBeenCalledWith({
        where: {
          date: new Date('2026-01-14'),
          country: 'AUS',
          category: 'T',
        },
        include: { races: true },
        orderBy: { name: 'asc' },
      });

      expect(result).toEqual(mockMeetings);
    });

    it('should filter by country and category', async () => {
      mockPrisma.meeting.findMany.mockResolvedValue([]);

      await service.getMeetingsByDate(new Date('2026-01-14'), {
        country: 'NZ',
        category: 'H',
      });

      expect(mockPrisma.meeting.findMany).toHaveBeenCalledWith({
        where: {
          date: new Date('2026-01-14'),
          country: 'NZ',
          category: 'H',
        },
        include: { races: true },
        orderBy: { name: 'asc' },
      });
    });
  });

  describe('updateMeeting', () => {
    it('should refresh meeting data from API', async () => {
      const mockApiResponse = {
        header: {
          title: 'Meeting',
          generated_time: '2026-01-14T01:00:00Z',
          url: '/meeting',
        },
        params: { enc: 'json', id: 'meeting-uuid' },
        data: {
          meetings: [
            {
              meeting: 'meeting-uuid',
              name: 'Flemington',
              date: '2026-01-14T00:00:00Z',
              track_condition: 'Soft',
              category: 'T' as const,
              category_name: 'Thoroughbred',
              country: 'AUS',
              state: 'VIC',
              races: [],
            },
          ],
        },
      };

      mockApiClient.getMeetingById.mockResolvedValue(mockApiResponse);
      mockPrisma.meeting.upsert.mockResolvedValue({} as any);

      await service.updateMeeting('meeting-uuid');

      expect(mockApiClient.getMeetingById).toHaveBeenCalledWith('meeting-uuid');
      // updateMeeting uses processMeeting which calls upsert
      expect(mockPrisma.meeting.upsert).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: 'meeting-uuid' },
          update: expect.objectContaining({
            trackCondition: 'Soft',
          }),
        })
      );
    });
  });

  describe('metrics and observability', () => {
    it('should emit metrics for successful operations', async () => {
      mockApiClient.getMeetings.mockResolvedValue({
        header: { title: 'Test', generated_time: '2026-01-14T01:00:00Z', url: '/' },
        params: { enc: 'json', date_from: '2026-01-14', date_to: '2026-01-14' },
        data: { meetings: [] },
      });

      await service.fetchAndStoreMeetings({
        category: 'T',
        country: 'AUS',
        date: new Date('2026-01-14'),
      });

      // Metrics should be emitted
      // This will be verified via mock assertions
    });
  });
});
