/**
 * @ai-summary Unit tests for StationsService
 */

import { StationsService } from '../../domain/stations.service';
import { StationsRepository } from '../../data/stations.repository';
import { googleMapsClient } from '../../external/google-maps/google-maps.client';
import {
  mockStations,
  mockSavedStations,
  mockUserId,
  searchCoordinates
} from '../fixtures/mock-stations';

jest.mock('../../data/stations.repository');
jest.mock('../../external/google-maps/google-maps.client');

describe('StationsService', () => {
  let service: StationsService;
  let mockRepository: jest.Mocked<StationsRepository>;

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

    mockRepository = {
      cacheStation: jest.fn().mockResolvedValue(undefined),
      getCachedStation: jest.fn(),
      saveStation: jest.fn(),
      getUserSavedStations: jest.fn().mockResolvedValue([]),
      updateSavedStation: jest.fn(),
      deleteSavedStation: jest.fn()
    } as unknown as jest.Mocked<StationsRepository>;

    (StationsRepository as jest.Mock).mockImplementation(() => mockRepository);

    (googleMapsClient.searchNearbyStations as jest.Mock) = jest.fn().mockResolvedValue(mockStations);

    service = new StationsService(mockRepository);
  });

  describe('searchNearbyStations', () => {
    it('should search for nearby stations and cache results', async () => {
      const result = await service.searchNearbyStations(
        {
          latitude: searchCoordinates.sanFrancisco.latitude,
          longitude: searchCoordinates.sanFrancisco.longitude,
          radius: 5000
        },
        mockUserId
      );

      expect(result.stations).toHaveLength(3);
      expect(result.stations[0]?.name).toBe('Shell Gas Station - Downtown');
      expect(mockRepository.cacheStation).toHaveBeenCalledTimes(3);
      expect(mockRepository.getUserSavedStations).toHaveBeenCalledWith(mockUserId);
    });

    it('should sort stations by distance', async () => {
      const stationsWithDistance = [
        { ...mockStations[0], distance: 500 },
        { ...mockStations[1], distance: 100 },
        { ...mockStations[2], distance: 2000 }
      ];

      (googleMapsClient.searchNearbyStations as jest.Mock).mockResolvedValue(
        stationsWithDistance
      );

      const result = await service.searchNearbyStations(
        {
          latitude: searchCoordinates.sanFrancisco.latitude,
          longitude: searchCoordinates.sanFrancisco.longitude
        },
        mockUserId
      );

      expect(result.stations[0]?.distance).toBe(100);
      expect(result.stations[1]?.distance).toBe(500);
      expect(result.stations[2]?.distance).toBe(2000);
    });

    it('should return search metadata', async () => {
      const result = await service.searchNearbyStations(
        {
          latitude: searchCoordinates.sanFrancisco.latitude,
          longitude: searchCoordinates.sanFrancisco.longitude,
          radius: 3000
        },
        mockUserId
      );

      expect(result.searchLocation.latitude).toBe(
        searchCoordinates.sanFrancisco.latitude
      );
      expect(result.searchLocation.longitude).toBe(
        searchCoordinates.sanFrancisco.longitude
      );
      expect(result.searchRadius).toBe(3000);
      expect(result.timestamp).toBeDefined();
    });

    it('should use default radius if not provided', async () => {
      await service.searchNearbyStations(
        {
          latitude: searchCoordinates.sanFrancisco.latitude,
          longitude: searchCoordinates.sanFrancisco.longitude
        },
        mockUserId
      );

      expect(mockRepository.cacheStation).toHaveBeenCalled();
    });

    it('should prioritize saved stations and include saved metadata', async () => {
      const savedStation = mockSavedStations[0];
      if (!savedStation) throw new Error('Mock saved station not found');
      mockRepository.getUserSavedStations.mockResolvedValue([savedStation]);

      const stationsWithDistance = [
        { ...mockStations[0], distance: 900 },
        { ...mockStations[1], distance: 100 }
      ];

      (googleMapsClient.searchNearbyStations as jest.Mock).mockResolvedValue(
        stationsWithDistance
      );

      const result = await service.searchNearbyStations(
        {
          latitude: searchCoordinates.sanFrancisco.latitude,
          longitude: searchCoordinates.sanFrancisco.longitude
        },
        mockUserId
      );

      expect(result.stations[0]?.placeId).toBe(savedStation.stationId);
      expect(result.stations[0]?.savedMetadata?.has93Octane).toBe(true);
    });
  });

  describe('saveStation', () => {
    it('should save a station from cache', async () => {
      const station = mockStations[0];
      if (!station) throw new Error('Mock station not found');
      const stationId = station.placeId;
      mockRepository.getCachedStation.mockResolvedValue(station);
      const savedStation = mockSavedStations[0];
      if (!savedStation) throw new Error('Mock saved station not found');
      mockRepository.saveStation.mockResolvedValue(savedStation);

      const result = await service.saveStation(stationId, mockUserId, {
        nickname: 'Work Gas Station'
      });

      expect(mockRepository.getCachedStation).toHaveBeenCalledWith(stationId);
      expect(mockRepository.saveStation).toHaveBeenCalledWith(
        mockUserId,
        stationId,
        { nickname: 'Work Gas Station' }
      );
      expect(result).toHaveProperty('id');
    });

    it('should throw error if station not in cache', async () => {
      mockRepository.getCachedStation.mockResolvedValue(null);

      await expect(
        service.saveStation('unknown-id', mockUserId)
      ).rejects.toThrow('Station not found');
    });

    it('should save station with custom metadata', async () => {
      const station = mockStations[0];
      if (!station) throw new Error('Mock station not found');
      const savedStation = mockSavedStations[0];
      if (!savedStation) throw new Error('Mock saved station not found');

      mockRepository.getCachedStation.mockResolvedValue(station);
      mockRepository.saveStation.mockResolvedValue(savedStation);

      await service.saveStation(station.placeId, mockUserId, {
        nickname: 'Favorite Station',
        notes: 'Best prices in area',
        isFavorite: true,
        has93Octane: true,
        has93OctaneEthanolFree: false
      });

      expect(mockRepository.saveStation).toHaveBeenCalledWith(mockUserId, station.placeId, {
        nickname: 'Favorite Station',
        notes: 'Best prices in area',
        isFavorite: true,
        has93Octane: true,
        has93OctaneEthanolFree: false
      });
    });
  });

  describe('getUserSavedStations', () => {
    it('should return all saved stations for user', async () => {
      const station = mockStations[0];
      if (!station) throw new Error('Mock station not found');

      mockRepository.getUserSavedStations.mockResolvedValue(mockSavedStations);
      mockRepository.getCachedStation.mockResolvedValue(station);

      const result = await service.getUserSavedStations(mockUserId);

      expect(mockRepository.getUserSavedStations).toHaveBeenCalledWith(mockUserId);
      expect(result).toBeDefined();
      expect(result.length).toBe(mockSavedStations.length);
    });

    it('should return empty array if user has no saved stations', async () => {
      mockRepository.getUserSavedStations.mockResolvedValue([]);

      const result = await service.getUserSavedStations('other-user');

      expect(result).toEqual([]);
    });
  });

  describe('updateSavedStation', () => {
    it('should update saved station metadata', async () => {
      const savedStation = mockSavedStations[0];
      const station = mockStations[0];
      if (!savedStation || !station) throw new Error('Mock data not found');

      mockRepository.updateSavedStation.mockResolvedValue(savedStation);
      mockRepository.getCachedStation.mockResolvedValue(station);

      const result = await service.updateSavedStation(savedStation.stationId, mockUserId, {
        has93Octane: true,
        has93OctaneEthanolFree: true
      });

      expect(mockRepository.updateSavedStation).toHaveBeenCalledWith(
        mockUserId,
        savedStation.stationId,
        {
          has93Octane: true,
          has93OctaneEthanolFree: true
        }
      );
      expect(result.station).toEqual(station);
    });

    it('should throw if saved station not found', async () => {
      mockRepository.updateSavedStation.mockResolvedValue(null);

      await expect(
        service.updateSavedStation('unknown', mockUserId, { nickname: 'Update' })
      ).rejects.toThrow('Saved station not found');
    });
  });

  describe('removeSavedStation', () => {
    it('should delete a saved station', async () => {
      const savedStation = mockSavedStations[0];
      if (!savedStation) throw new Error('Mock saved station not found');

      mockRepository.deleteSavedStation.mockResolvedValue(true);

      await service.removeSavedStation(
        savedStation.stationId,
        mockUserId
      );

      expect(mockRepository.deleteSavedStation).toHaveBeenCalledWith(
        mockUserId,
        savedStation.stationId
      );
    });

    it('should throw error if station not found', async () => {
      mockRepository.deleteSavedStation.mockResolvedValue(false);

      await expect(
        service.removeSavedStation('non-existent', mockUserId)
      ).rejects.toThrow('Saved station not found');
    });

    it('should verify user isolation', async () => {
      const savedStation = mockSavedStations[0];
      if (!savedStation) throw new Error('Mock saved station not found');

      mockRepository.deleteSavedStation.mockResolvedValue(true);

      await service.removeSavedStation(savedStation.stationId, 'other-user');

      expect(mockRepository.deleteSavedStation).toHaveBeenCalledWith(
        'other-user',
        savedStation.stationId
      );
    });
  });
});
