/**
 * @ai-summary Unit tests for VehiclesService
 * @ai-context Tests business logic with mocked dependencies
 */

import { VehiclesService } from '../../domain/vehicles.service';
import { VehiclesRepository } from '../../data/vehicles.repository';
import { cacheService } from '../../../../core/config/redis';
import * as platformModule from '../../../platform';

// Mock dependencies
jest.mock('../../data/vehicles.repository');
jest.mock('../../../../core/config/redis');
jest.mock('../../../platform', () => ({
  getVehicleDataService: jest.fn(),
  getPool: jest.fn()
}));

const mockRepository = jest.mocked(VehiclesRepository);
const mockCacheService = jest.mocked(cacheService);
const mockGetVehicleDataService = jest.mocked(platformModule.getVehicleDataService);
const mockGetPool = jest.mocked(platformModule.getPool);

describe('VehiclesService', () => {
  let service: VehiclesService;
  let repositoryInstance: jest.Mocked<VehiclesRepository>;
  let vehicleDataServiceMock: {
    getYears: jest.Mock;
    getMakes: jest.Mock;
    getModels: jest.Mock;
    getTrims: jest.Mock;
    getEngines: jest.Mock;
  };

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

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

    mockGetVehicleDataService.mockReturnValue(vehicleDataServiceMock as any);
    mockGetPool.mockReturnValue('mock-pool' as any);

    repositoryInstance = {
      create: jest.fn(),
      findByUserId: jest.fn(),
      findById: jest.fn(),
      findByUserAndVIN: jest.fn(),
      update: jest.fn(),
      softDelete: jest.fn(),
      countByUserId: jest.fn(),
    } as any;

    const mockPool = {
      query: jest.fn(),
      connect: jest.fn().mockResolvedValue({
        query: jest.fn(),
        release: jest.fn(),
      }),
    } as any;

    mockRepository.mockImplementation(() => repositoryInstance);
    service = new VehiclesService(repositoryInstance, mockPool);
  });

  describe('dropdown data integration', () => {
    it('retrieves years from platform service', async () => {
      vehicleDataServiceMock.getYears.mockResolvedValue([2024, 2023]);

      const result = await service.getDropdownYears();

      expect(mockGetVehicleDataService).toHaveBeenCalled();
      expect(vehicleDataServiceMock.getYears).toHaveBeenCalledWith('mock-pool');
      expect(result).toEqual([2024, 2023]);
    });

    it('retrieves makes scoped to year', async () => {
      vehicleDataServiceMock.getMakes.mockResolvedValue([{ id: 10, name: 'Honda' }]);

      const result = await service.getDropdownMakes(2024);

      expect(vehicleDataServiceMock.getMakes).toHaveBeenCalledWith('mock-pool', 2024);
      expect(result).toEqual([{ id: 10, name: 'Honda' }]);
    });

    it('retrieves models scoped to year and make', async () => {
      vehicleDataServiceMock.getModels.mockResolvedValue([{ id: 20, name: 'Civic' }]);

      const result = await service.getDropdownModels(2024, 'Honda');

      expect(vehicleDataServiceMock.getModels).toHaveBeenCalledWith('mock-pool', 2024, 'Honda');
      expect(result).toEqual([{ id: 20, name: 'Civic' }]);
    });

    it('retrieves trims scoped to year, make, and model', async () => {
      vehicleDataServiceMock.getTrims.mockResolvedValue([{ id: 30, name: 'Sport' }]);

      const result = await service.getDropdownTrims(2024, 'Honda', 'Civic');

      expect(vehicleDataServiceMock.getTrims).toHaveBeenCalledWith('mock-pool', 2024, 'Honda', 'Civic');
      expect(result).toEqual([{ id: 30, name: 'Sport' }]);
    });

    it('retrieves engines scoped to selection', async () => {
      vehicleDataServiceMock.getEngines.mockResolvedValue([{ id: 40, name: '2.0L Turbo' }]);

      const result = await service.getDropdownEngines(2024, 'Honda', 'Civic', 'Sport');

      expect(vehicleDataServiceMock.getEngines).toHaveBeenCalledWith('mock-pool', 2024, 'Honda', 'Civic', 'Sport');
      expect(result).toEqual([{ id: 40, name: '2.0L Turbo' }]);
    });

    it('returns static transmission options', async () => {
      const result = await service.getDropdownTransmissions(2024, 'Honda', 'Civic', 'Sport');

      expect(result).toEqual([
        { id: 1, name: 'Automatic' },
        { id: 2, name: 'Manual' }
      ]);
    });
  });

  describe('createVehicle', () => {
    const mockVehicleData = {
      vin: '1HGBH41JXMN109186',
      nickname: 'My Car',
      color: 'Blue',
      odometerReading: 50000,
    };

    const mockCreatedVehicle = {
      id: 'vehicle-id-123',
      userId: 'user-123',
      vin: '1HGBH41JXMN109186',
      make: undefined,
      model: undefined,
      year: undefined,
      nickname: 'My Car',
      color: 'Blue',
      licensePlate: undefined,
      odometerReading: 50000,
      isActive: true,
      deletedAt: undefined,
      createdAt: new Date('2024-01-01T00:00:00Z'),
      updatedAt: new Date('2024-01-01T00:00:00Z'),
    };

    it('should create a vehicle with user-provided VIN', async () => {
      repositoryInstance.findByUserAndVIN.mockResolvedValue(null);
      repositoryInstance.create.mockResolvedValue(mockCreatedVehicle);
      mockCacheService.del.mockResolvedValue(undefined);

      const result = await service.createVehicle(mockVehicleData, 'user-123');

      expect(repositoryInstance.findByUserAndVIN).toHaveBeenCalledWith('user-123', '1HGBH41JXMN109186');
      expect(repositoryInstance.create).toHaveBeenCalledWith({
        ...mockVehicleData,
        userId: 'user-123',
        make: undefined,
        model: undefined,
      });
      expect(result.id).toBe('vehicle-id-123');
    });

    it('should reject invalid VIN format', async () => {
      const invalidVin = { ...mockVehicleData, vin: 'INVALID' };

      await expect(service.createVehicle(invalidVin, 'user-123')).rejects.toThrow('Invalid VIN format');
    });

    it('should reject duplicate VIN for same user', async () => {
      repositoryInstance.findByUserAndVIN.mockResolvedValue(mockCreatedVehicle);

      await expect(service.createVehicle(mockVehicleData, 'user-123')).rejects.toThrow('Vehicle with this VIN already exists');
    });
  });

  describe('getUserVehicles', () => {
    it('should return cached vehicles if available', async () => {
      const cachedVehicles = [{ id: 'vehicle-1', vin: '1HGBH41JXMN109186' }];
      mockCacheService.get.mockResolvedValue(cachedVehicles);

      const result = await service.getUserVehicles('user-123');

      expect(mockCacheService.get).toHaveBeenCalledWith('vehicles:user:user-123');
      expect(result).toBe(cachedVehicles);
      expect(repositoryInstance.findByUserId).not.toHaveBeenCalled();
    });

    it('should fetch from database and cache if not cached', async () => {
      const mockVehicles = [
        {
          id: 'vehicle-id-123',
          userId: 'user-123',
          vin: '1HGBH41JXMN109186',
          make: 'Honda',
          model: 'Civic',
          year: 2021,
          nickname: 'My Car',
          color: 'Blue',
          licensePlate: undefined,
          odometerReading: 50000,
          isActive: true,
          deletedAt: undefined,
          createdAt: new Date('2024-01-01T00:00:00Z'),
          updatedAt: new Date('2024-01-01T00:00:00Z'),
        }
      ];

      mockCacheService.get.mockResolvedValue(null);
      repositoryInstance.findByUserId.mockResolvedValue(mockVehicles);
      mockCacheService.set.mockResolvedValue(undefined);

      const result = await service.getUserVehicles('user-123');

      expect(repositoryInstance.findByUserId).toHaveBeenCalledWith('user-123');
      expect(mockCacheService.set).toHaveBeenCalledWith('vehicles:user:user-123', expect.any(Array), 300);
      expect(result).toHaveLength(1);
      expect(result[0].id).toBe('vehicle-id-123');
    });
  });

  describe('getVehicle', () => {
    const mockVehicle = {
      id: 'vehicle-id-123',
      userId: 'user-123',
      vin: '1HGBH41JXMN109186',
      make: 'Honda',
      model: 'Civic',
      year: 2021,
      nickname: 'My Car',
      color: 'Blue',
      licensePlate: undefined,
      odometerReading: 50000,
      isActive: true,
      deletedAt: undefined,
      createdAt: new Date('2024-01-01T00:00:00Z'),
      updatedAt: new Date('2024-01-01T00:00:00Z'),
    };

    it('should return vehicle if found and owned by user', async () => {
      repositoryInstance.findById.mockResolvedValue(mockVehicle);

      const result = await service.getVehicle('vehicle-id-123', 'user-123');

      expect(repositoryInstance.findById).toHaveBeenCalledWith('vehicle-id-123');
      expect(result.id).toBe('vehicle-id-123');
    });

    it('should throw error if vehicle not found', async () => {
      repositoryInstance.findById.mockResolvedValue(null);

      await expect(service.getVehicle('vehicle-id-123', 'user-123')).rejects.toThrow('Vehicle not found');
    });

    it('should throw error if user is not owner', async () => {
      repositoryInstance.findById.mockResolvedValue({ ...mockVehicle, userId: 'other-user' });

      await expect(service.getVehicle('vehicle-id-123', 'user-123')).rejects.toThrow('Unauthorized');
    });
  });

  describe('updateVehicle', () => {
    const mockVehicle = {
      id: 'vehicle-id-123',
      userId: 'user-123',
      vin: '1HGBH41JXMN109186',
      make: 'Honda',
      model: 'Civic',
      year: 2021,
      nickname: 'My Car',
      color: 'Blue',
      licensePlate: undefined,
      odometerReading: 50000,
      isActive: true,
      deletedAt: undefined,
      createdAt: new Date('2024-01-01T00:00:00Z'),
      updatedAt: new Date('2024-01-01T00:00:00Z'),
    };

    it('should update vehicle successfully', async () => {
      const updateData = { nickname: 'Updated Car', color: 'Red' };
      const updatedVehicle = { ...mockVehicle, ...updateData };

      repositoryInstance.findById.mockResolvedValue(mockVehicle);
      repositoryInstance.update.mockResolvedValue(updatedVehicle);
      mockCacheService.del.mockResolvedValue(undefined);

      const result = await service.updateVehicle('vehicle-id-123', updateData, 'user-123');

      expect(repositoryInstance.findById).toHaveBeenCalledWith('vehicle-id-123');
      expect(repositoryInstance.update).toHaveBeenCalledWith('vehicle-id-123', updateData);
      expect(mockCacheService.del).toHaveBeenCalledWith('vehicles:user:user-123');
      expect(result.nickname).toBe('Updated Car');
      expect(result.color).toBe('Red');
    });

    it('should throw error if vehicle not found', async () => {
      repositoryInstance.findById.mockResolvedValue(null);

      await expect(service.updateVehicle('vehicle-id-123', {}, 'user-123')).rejects.toThrow('Vehicle not found');
    });

    it('should throw error if user is not owner', async () => {
      repositoryInstance.findById.mockResolvedValue({ ...mockVehicle, userId: 'other-user' });

      await expect(service.updateVehicle('vehicle-id-123', {}, 'user-123')).rejects.toThrow('Unauthorized');
    });
  });

  describe('deleteVehicle', () => {
    const mockVehicle = {
      id: 'vehicle-id-123',
      userId: 'user-123',
      vin: '1HGBH41JXMN109186',
      make: 'Honda',
      model: 'Civic',
      year: 2021,
      nickname: 'My Car',
      color: 'Blue',
      licensePlate: undefined,
      odometerReading: 50000,
      isActive: true,
      deletedAt: undefined,
      createdAt: new Date('2024-01-01T00:00:00Z'),
      updatedAt: new Date('2024-01-01T00:00:00Z'),
    };

    it('should delete vehicle successfully', async () => {
      repositoryInstance.findById.mockResolvedValue(mockVehicle);
      repositoryInstance.softDelete.mockResolvedValue(true);
      mockCacheService.del.mockResolvedValue(undefined);

      await service.deleteVehicle('vehicle-id-123', 'user-123');

      expect(repositoryInstance.findById).toHaveBeenCalledWith('vehicle-id-123');
      expect(repositoryInstance.softDelete).toHaveBeenCalledWith('vehicle-id-123');
      expect(mockCacheService.del).toHaveBeenCalledWith('vehicles:user:user-123');
    });

    it('should throw error if vehicle not found', async () => {
      repositoryInstance.findById.mockResolvedValue(null);

      await expect(service.deleteVehicle('vehicle-id-123', 'user-123')).rejects.toThrow('Vehicle not found');
    });

    it('should throw error if user is not owner', async () => {
      repositoryInstance.findById.mockResolvedValue({ ...mockVehicle, userId: 'other-user' });

      await expect(service.deleteVehicle('vehicle-id-123', 'user-123')).rejects.toThrow('Unauthorized');
    });
  });

  describe('getTCO', () => {
    const mockVehicle = {
      id: 'vehicle-id-123',
      userId: 'user-123',
      vin: '1HGBH41JXMN109186',
      make: 'Honda',
      model: 'Civic',
      year: 2021,
      odometerReading: 50000,
      isActive: true,
      purchasePrice: 25000,
      purchaseDate: '2022-01-15',
      insuranceCost: 150,
      insuranceInterval: 'monthly' as const,
      registrationCost: 200,
      registrationInterval: 'annual' as const,
      tcoEnabled: true,
      createdAt: new Date('2024-01-01T00:00:00Z'),
      updatedAt: new Date('2024-01-01T00:00:00Z'),
    };

    it('should throw error if vehicle not found', async () => {
      repositoryInstance.findById.mockResolvedValue(null);

      await expect(service.getTCO('vehicle-id-123', 'user-123')).rejects.toThrow('Vehicle not found');
    });

    it('should throw error if user is not owner', async () => {
      repositoryInstance.findById.mockResolvedValue({ ...mockVehicle, userId: 'other-user' });

      await expect(service.getTCO('vehicle-id-123', 'user-123')).rejects.toThrow('Unauthorized');
    });

    it('should return TCO with all cost components', async () => {
      repositoryInstance.findById.mockResolvedValue(mockVehicle);

      const result = await service.getTCO('vehicle-id-123', 'user-123');

      expect(result.vehicleId).toBe('vehicle-id-123');
      expect(result.purchasePrice).toBe(25000);
      expect(result.lifetimeTotal).toBeGreaterThan(25000);
      expect(result.distanceUnit).toBeDefined();
      expect(result.currencyCode).toBeDefined();
    });

    it('should handle missing optional TCO fields gracefully', async () => {
      const vehicleWithoutTCO = {
        ...mockVehicle,
        purchasePrice: undefined,
        purchaseDate: undefined,
        insuranceCost: undefined,
        insuranceInterval: undefined,
        registrationCost: undefined,
        registrationInterval: undefined,
      };
      repositoryInstance.findById.mockResolvedValue(vehicleWithoutTCO);

      const result = await service.getTCO('vehicle-id-123', 'user-123');

      expect(result.purchasePrice).toBe(0);
      expect(result.insuranceCosts).toBe(0);
      expect(result.registrationCosts).toBe(0);
    });

    it('should return zero costPerDistance when odometer is zero', async () => {
      const vehicleWithZeroOdometer = { ...mockVehicle, odometerReading: 0 };
      repositoryInstance.findById.mockResolvedValue(vehicleWithZeroOdometer);

      const result = await service.getTCO('vehicle-id-123', 'user-123');

      expect(result.costPerDistance).toBe(0);
    });

    it('should calculate costPerDistance correctly', async () => {
      const vehicleWith100Miles = {
        ...mockVehicle,
        odometerReading: 100,
        purchasePrice: 1000,
        insuranceCost: undefined,
        registrationCost: undefined,
        purchaseDate: undefined,
      };
      repositoryInstance.findById.mockResolvedValue(vehicleWith100Miles);

      const result = await service.getTCO('vehicle-id-123', 'user-123');

      // With only $1000 purchase price and 100 miles, costPerDistance should be ~$10/mile
      // (plus any fuel/maintenance costs which may be 0 in mock)
      expect(result.costPerDistance).toBeGreaterThan(0);
    });
  });

  describe('normalizeRecurringCost (via getTCO)', () => {
    it('should normalize monthly costs correctly', async () => {
      // Vehicle purchased 12 months ago with $100/month insurance
      const oneYearAgo = new Date();
      oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);

      const mockVehicle = {
        id: 'vehicle-id-123',
        userId: 'user-123',
        odometerReading: 10000,
        isActive: true,
        purchasePrice: 0,
        purchaseDate: oneYearAgo.toISOString().split('T')[0],
        insuranceCost: 100,
        insuranceInterval: 'monthly' as const,
        registrationCost: 0,
        tcoEnabled: true,
        createdAt: new Date(),
        updatedAt: new Date(),
      };
      repositoryInstance.findById.mockResolvedValue(mockVehicle);

      const result = await service.getTCO('vehicle-id-123', 'user-123');

      // 12 months * $100/month = $1200 insurance
      expect(result.insuranceCosts).toBeCloseTo(1200, 0);
    });

    it('should normalize annual costs correctly', async () => {
      // Vehicle purchased 24 months ago with $200/year registration
      const twoYearsAgo = new Date();
      twoYearsAgo.setFullYear(twoYearsAgo.getFullYear() - 2);

      const mockVehicle = {
        id: 'vehicle-id-123',
        userId: 'user-123',
        odometerReading: 20000,
        isActive: true,
        purchasePrice: 0,
        purchaseDate: twoYearsAgo.toISOString().split('T')[0],
        insuranceCost: 0,
        registrationCost: 200,
        registrationInterval: 'annual' as const,
        tcoEnabled: true,
        createdAt: new Date(),
        updatedAt: new Date(),
      };
      repositoryInstance.findById.mockResolvedValue(mockVehicle);

      const result = await service.getTCO('vehicle-id-123', 'user-123');

      // 2 years * $200/year = $400 registration
      expect(result.registrationCosts).toBeCloseTo(400, 0);
    });

    it('should handle semi-annual costs correctly', async () => {
      // Vehicle purchased 12 months ago with $300/6months insurance
      const oneYearAgo = new Date();
      oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);

      const mockVehicle = {
        id: 'vehicle-id-123',
        userId: 'user-123',
        odometerReading: 10000,
        isActive: true,
        purchasePrice: 0,
        purchaseDate: oneYearAgo.toISOString().split('T')[0],
        insuranceCost: 300,
        insuranceInterval: 'semi_annual' as const,
        registrationCost: 0,
        tcoEnabled: true,
        createdAt: new Date(),
        updatedAt: new Date(),
      };
      repositoryInstance.findById.mockResolvedValue(mockVehicle);

      const result = await service.getTCO('vehicle-id-123', 'user-123');

      // 12 months / 12 * 2 payments/year * $300 = $600 insurance
      expect(result.insuranceCosts).toBeCloseTo(600, 0);
    });

    it('should guard against division by zero with new purchase', async () => {
      // Vehicle purchased today
      const today = new Date().toISOString().split('T')[0];

      const mockVehicle = {
        id: 'vehicle-id-123',
        userId: 'user-123',
        odometerReading: 0,
        isActive: true,
        purchasePrice: 30000,
        purchaseDate: today,
        insuranceCost: 100,
        insuranceInterval: 'monthly' as const,
        registrationCost: 0,
        tcoEnabled: true,
        createdAt: new Date(),
        updatedAt: new Date(),
      };
      repositoryInstance.findById.mockResolvedValue(mockVehicle);

      // Should not throw - Math.max(1, monthsOwned) should prevent division by zero
      const result = await service.getTCO('vehicle-id-123', 'user-123');

      expect(result.lifetimeTotal).toBeGreaterThanOrEqual(30000);
      // Insurance should be calculated for at least 1 month
      expect(result.insuranceCosts).toBeGreaterThan(0);
    });

    it('should handle future purchase date gracefully', async () => {
      // Vehicle with purchase date in the future (should treat as 0 months)
      const futureDate = new Date();
      futureDate.setFullYear(futureDate.getFullYear() + 1);

      const mockVehicle = {
        id: 'vehicle-id-123',
        userId: 'user-123',
        odometerReading: 0,
        isActive: true,
        purchasePrice: 30000,
        purchaseDate: futureDate.toISOString().split('T')[0],
        insuranceCost: 100,
        insuranceInterval: 'monthly' as const,
        registrationCost: 0,
        tcoEnabled: true,
        createdAt: new Date(),
        updatedAt: new Date(),
      };
      repositoryInstance.findById.mockResolvedValue(mockVehicle);

      // Should not throw
      const result = await service.getTCO('vehicle-id-123', 'user-123');

      expect(result.purchasePrice).toBe(30000);
      // With future date, monthsOwned returns 0, but Math.max(1, 0) = 1
      // so insurance should still calculate for minimum 1 payment period
      expect(result.insuranceCosts).toBeGreaterThanOrEqual(0);
    });
  });
});
