/**
 * @ai-summary Unit tests for vehicle data service
 * @ai-context Tests caching behavior for hierarchical vehicle data
 */
import { Pool } from 'pg';
import { VehicleDataService } from '../../domain/vehicle-data.service';
import { PlatformCacheService } from '../../domain/platform-cache.service';
import { VehicleDataRepository } from '../../data/vehicle-data.repository';

jest.mock('../../data/vehicle-data.repository');
jest.mock('../../domain/platform-cache.service');

describe('VehicleDataService', () => {
  let service: VehicleDataService;
  let mockCache: jest.Mocked<PlatformCacheService>;
  let mockRepository: jest.Mocked<VehicleDataRepository>;
  let mockPool: jest.Mocked<Pool>;

  beforeEach(() => {
    mockCache = {
      getYears: jest.fn(),
      setYears: jest.fn(),
      getMakes: jest.fn(),
      setMakes: jest.fn(),
      getModels: jest.fn(),
      setModels: jest.fn(),
      getTrims: jest.fn(),
      setTrims: jest.fn(),
      getEngines: jest.fn(),
      setEngines: jest.fn()
    } as any;

    mockRepository = {
      getYears: jest.fn(),
      getMakes: jest.fn(),
      getModels: jest.fn(),
      getTrims: jest.fn(),
      getEngines: jest.fn()
    } as any;

    mockPool = {} as any;
    service = new VehicleDataService(mockCache, mockRepository);
  });

  describe('getYears', () => {
    it('should return cached years if available', async () => {
      const mockYears = [2024, 2023, 2022];
      mockCache.getYears.mockResolvedValue(mockYears);

      const result = await service.getYears(mockPool);

      expect(result).toEqual(mockYears);
      expect(mockCache.getYears).toHaveBeenCalled();
      expect(mockRepository.getYears).not.toHaveBeenCalled();
    });

    it('should fetch from repository and cache when cache misses', async () => {
      const mockYears = [2024, 2023, 2022];
      mockCache.getYears.mockResolvedValue(null);
      mockRepository.getYears.mockResolvedValue(mockYears);

      const result = await service.getYears(mockPool);

      expect(result).toEqual(mockYears);
      expect(mockRepository.getYears).toHaveBeenCalledWith(mockPool);
      expect(mockCache.setYears).toHaveBeenCalledWith(mockYears);
    });
  });

  describe('getMakes', () => {
    const year = 2024;
    const mockMakes = [
      { id: 1, name: 'Honda' },
      { id: 2, name: 'Toyota' }
    ];

    it('should return cached makes if available', async () => {
      mockCache.getMakes.mockResolvedValue(mockMakes);

      const result = await service.getMakes(mockPool, year);

      expect(result).toEqual(mockMakes);
      expect(mockCache.getMakes).toHaveBeenCalledWith(year);
      expect(mockRepository.getMakes).not.toHaveBeenCalled();
    });

    it('should fetch from repository and cache when cache misses', async () => {
      mockCache.getMakes.mockResolvedValue(null);
      mockRepository.getMakes.mockResolvedValue(mockMakes);

      const result = await service.getMakes(mockPool, year);

      expect(result).toEqual(mockMakes);
      expect(mockRepository.getMakes).toHaveBeenCalledWith(mockPool, year);
      expect(mockCache.setMakes).toHaveBeenCalledWith(year, mockMakes);
    });
  });

  describe('getModels', () => {
    const year = 2024;
    const makeId = 1;
    const mockModels = [
      { id: 101, name: 'Civic' },
      { id: 102, name: 'Accord' }
    ];

    it('should return cached models if available', async () => {
      mockCache.getModels.mockResolvedValue(mockModels);

      const result = await service.getModels(mockPool, year, makeId);

      expect(result).toEqual(mockModels);
      expect(mockCache.getModels).toHaveBeenCalledWith(year, makeId);
      expect(mockRepository.getModels).not.toHaveBeenCalled();
    });

    it('should fetch from repository and cache when cache misses', async () => {
      mockCache.getModels.mockResolvedValue(null);
      mockRepository.getModels.mockResolvedValue(mockModels);

      const result = await service.getModels(mockPool, year, makeId);

      expect(result).toEqual(mockModels);
      expect(mockRepository.getModels).toHaveBeenCalledWith(mockPool, year, makeId);
      expect(mockCache.setModels).toHaveBeenCalledWith(year, makeId, mockModels);
    });
  });

  describe('getTrims', () => {
    const year = 2024;
    const modelId = 101;
    const mockTrims = [
      { id: 1001, name: 'LX' },
      { id: 1002, name: 'EX' }
    ];

    it('should return cached trims if available', async () => {
      mockCache.getTrims.mockResolvedValue(mockTrims);

      const result = await service.getTrims(mockPool, year, modelId);

      expect(result).toEqual(mockTrims);
      expect(mockCache.getTrims).toHaveBeenCalledWith(year, modelId);
      expect(mockRepository.getTrims).not.toHaveBeenCalled();
    });

    it('should fetch from repository and cache when cache misses', async () => {
      mockCache.getTrims.mockResolvedValue(null);
      mockRepository.getTrims.mockResolvedValue(mockTrims);

      const result = await service.getTrims(mockPool, year, modelId);

      expect(result).toEqual(mockTrims);
      expect(mockRepository.getTrims).toHaveBeenCalledWith(mockPool, year, modelId);
      expect(mockCache.setTrims).toHaveBeenCalledWith(year, modelId, mockTrims);
    });
  });

  describe('getEngines', () => {
    const year = 2024;
    const modelId = 101;
    const trimId = 1001;
    const mockEngines = [
      { id: 10001, name: '2.0L I4' },
      { id: 10002, name: '1.5L Turbo I4' }
    ];

    it('should return cached engines if available', async () => {
      mockCache.getEngines.mockResolvedValue(mockEngines);

      const result = await service.getEngines(mockPool, year, modelId, trimId);

      expect(result).toEqual(mockEngines);
      expect(mockCache.getEngines).toHaveBeenCalledWith(year, modelId, trimId);
      expect(mockRepository.getEngines).not.toHaveBeenCalled();
    });

    it('should fetch from repository and cache when cache misses', async () => {
      mockCache.getEngines.mockResolvedValue(null);
      mockRepository.getEngines.mockResolvedValue(mockEngines);

      const result = await service.getEngines(mockPool, year, modelId, trimId);

      expect(result).toEqual(mockEngines);
      expect(mockRepository.getEngines).toHaveBeenCalledWith(mockPool, year, modelId, trimId);
      expect(mockCache.setEngines).toHaveBeenCalledWith(year, modelId, trimId, mockEngines);
    });
  });
});
