/**
 * @ai-summary Unit tests for station matching from receipt merchant names
 */

// Mock config-loader before any imports that use it
jest.mock('../../../../core/config/config-loader', () => ({
  appConfig: {
    secrets: { google_maps_api_key: 'mock-api-key' },
    getDatabaseUrl: () => 'postgresql://mock:mock@localhost/mock',
    getRedisUrl: () => 'redis://localhost',
    get: () => ({}),
  },
}));
jest.mock('axios');
jest.mock('../../../../core/config/redis');
jest.mock('../../../../core/logging/logger');
jest.mock('../../data/stations.repository');
jest.mock('../../external/google-maps/google-maps.client', () => {
  const { GoogleMapsClient } = jest.requireActual('../../external/google-maps/google-maps.client');
  return {
    GoogleMapsClient,
    googleMapsClient: {
      searchNearbyStations: jest.fn(),
      searchStationByName: jest.fn(),
      fetchPhoto: jest.fn(),
    },
  };
});

import axios from 'axios';
import { GoogleMapsClient } from '../../external/google-maps/google-maps.client';
import { StationsService } from '../../domain/stations.service';
import { StationsRepository } from '../../data/stations.repository';
import { googleMapsClient } from '../../external/google-maps/google-maps.client';
import { logger } from '../../../../core/logging/logger';
import { mockStations } from '../fixtures/mock-stations';

describe('Station Matching from Receipt', () => {
  describe('GoogleMapsClient.searchStationByName', () => {
    let client: GoogleMapsClient;
    let mockAxios: jest.Mocked<typeof axios>;

    beforeEach(() => {
      jest.clearAllMocks();
      mockAxios = axios as jest.Mocked<typeof axios>;
      client = new GoogleMapsClient();
    });

    it('should match a known station name like "Shell"', async () => {
      mockAxios.get.mockResolvedValue({
        data: {
          results: [
            {
              place_id: 'ChIJ_shell_match',
              name: 'Shell Gas Station',
              formatted_address: '123 Main St, San Francisco, CA 94105',
              geometry: { location: { lat: 37.7749, lng: -122.4194 } },
              rating: 4.2,
              photos: [{ photo_reference: 'shell-photo-ref' }],
              opening_hours: { open_now: true },
              types: ['gas_station'],
            },
          ],
          status: 'OK',
        },
      });

      const result = await client.searchStationByName('Shell');

      expect(result).not.toBeNull();
      expect(result?.placeId).toBe('ChIJ_shell_match');
      expect(result?.name).toBe('Shell Gas Station');
      expect(result?.address).toBe('123 Main St, San Francisco, CA 94105');
      expect(mockAxios.get).toHaveBeenCalledWith(
        expect.stringContaining('textsearch/json'),
        expect.objectContaining({
          params: expect.objectContaining({
            query: 'Shell gas station',
            type: 'gas_station',
          }),
        })
      );
    });

    it('should match abbreviated names like "COSTCO #123"', async () => {
      mockAxios.get.mockResolvedValue({
        data: {
          results: [
            {
              place_id: 'ChIJ_costco_match',
              name: 'Costco Gasoline',
              formatted_address: '2000 El Camino Real, Redwood City, CA',
              geometry: { location: { lat: 37.4849, lng: -122.2278 } },
              rating: 4.5,
              types: ['gas_station'],
            },
          ],
          status: 'OK',
        },
      });

      const result = await client.searchStationByName('COSTCO #123');

      expect(result).not.toBeNull();
      expect(result?.name).toBe('Costco Gasoline');
      expect(result?.placeId).toBe('ChIJ_costco_match');
    });

    it('should match "BP" station name', async () => {
      mockAxios.get.mockResolvedValue({
        data: {
          results: [
            {
              place_id: 'ChIJ_bp_match',
              name: 'BP',
              formatted_address: '500 Market St, San Francisco, CA',
              geometry: { location: { lat: 37.79, lng: -122.40 } },
              types: ['gas_station'],
            },
          ],
          status: 'OK',
        },
      });

      const result = await client.searchStationByName('BP');

      expect(result).not.toBeNull();
      expect(result?.name).toBe('BP');
    });

    it('should return null when no match is found', async () => {
      mockAxios.get.mockResolvedValue({
        data: {
          results: [],
          status: 'ZERO_RESULTS',
        },
      });

      const result = await client.searchStationByName('Unknown Station XYZ123');

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

    it('should return null gracefully on API error', async () => {
      mockAxios.get.mockRejectedValue(new Error('Network error'));

      const result = await client.searchStationByName('Shell');

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

    it('should return null on API denial', async () => {
      mockAxios.get.mockResolvedValue({
        data: {
          results: [],
          status: 'REQUEST_DENIED',
          error_message: 'Invalid key',
        },
      });

      const result = await client.searchStationByName('Shell');

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

    it('should return null with logged warning on Places API timeout', async () => {
      const timeoutError = new Error('timeout of 5000ms exceeded') as any;
      timeoutError.code = 'ECONNABORTED';
      mockAxios.get.mockRejectedValue(timeoutError);

      const mockLogger = logger as jest.Mocked<typeof logger>;

      const result = await client.searchStationByName('Shell');

      expect(result).toBeNull();
      expect(mockLogger.warn).toHaveBeenCalledWith(
        'Station name search timed out',
        expect.objectContaining({ merchantName: 'Shell', timeoutMs: 5000 })
      );
      expect(mockLogger.error).not.toHaveBeenCalled();
    });

    it('should include rating and photo reference when available', async () => {
      mockAxios.get.mockResolvedValue({
        data: {
          results: [
            {
              place_id: 'ChIJ_rated',
              name: 'Chevron',
              formatted_address: '789 Oak Ave, Portland, OR',
              geometry: { location: { lat: 45.52, lng: -122.68 } },
              rating: 4.7,
              photos: [{ photo_reference: 'chevron-photo' }],
              opening_hours: { open_now: false },
              types: ['gas_station'],
            },
          ],
          status: 'OK',
        },
      });

      const result = await client.searchStationByName('Chevron');

      expect(result?.rating).toBe(4.7);
      expect(result?.photoReference).toBe('chevron-photo');
      expect(result?.isOpen).toBe(false);
    });
  });

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

    const mockSearchByName = googleMapsClient.searchStationByName as jest.Mock;

    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>;

      service = new StationsService(mockRepository);
    });

    it('should return matched station for known merchant name', async () => {
      const matchedStation = mockStations[0]!;
      mockSearchByName.mockResolvedValue(matchedStation);

      const result = await service.matchStationFromReceipt('Shell');

      expect(result.matched).toBe(true);
      expect(result.station).not.toBeNull();
      expect(result.station?.name).toBe('Shell Gas Station - Downtown');
      expect(mockRepository.cacheStation).toHaveBeenCalledWith(matchedStation);
    });

    it('should return no match for unknown merchant', async () => {
      mockSearchByName.mockResolvedValue(null);

      const result = await service.matchStationFromReceipt('Unknown Store');

      expect(result.matched).toBe(false);
      expect(result.station).toBeNull();
      expect(mockRepository.cacheStation).not.toHaveBeenCalled();
    });

    it('should handle empty merchant name', async () => {
      const result = await service.matchStationFromReceipt('');

      expect(result.matched).toBe(false);
      expect(result.station).toBeNull();
    });

    it('should handle whitespace-only merchant name', async () => {
      const result = await service.matchStationFromReceipt('   ');

      expect(result.matched).toBe(false);
      expect(result.station).toBeNull();
    });

    it('should cache matched station for future saveStation calls', async () => {
      const matchedStation = mockStations[1]!;
      mockSearchByName.mockResolvedValue(matchedStation);

      await service.matchStationFromReceipt('Chevron');

      expect(mockRepository.cacheStation).toHaveBeenCalledWith(matchedStation);
    });
  });
});
