// src/services/googleGeocodingService.server.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

// Un-mock the module we are testing to ensure we use the real implementation.
vi.unmock('./googleGeocodingService.server');

// Mock the logger to prevent console output and allow for assertions
vi.mock('./logger.server', () => ({
  logger: {
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
    debug: vi.fn(),
  },
}));

// Import the service to be tested and its mocked dependencies
import { GoogleGeocodingService } from './googleGeocodingService.server';
import { logger as mockLogger } from './logger.server';

describe('Google Geocoding Service', () => {
  let googleGeocodingService: GoogleGeocodingService;
  const originalEnv = process.env;

  beforeEach(() => {
    vi.clearAllMocks();
    // Mock the global fetch function before each test
    vi.stubGlobal('fetch', vi.fn());
    // Restore process.env to a clean state for each test
    process.env = { ...originalEnv };
    googleGeocodingService = new GoogleGeocodingService();
  });

  afterEach(() => {
    // Restore original environment variables after each test
    process.env = originalEnv;
  });

  it('should return coordinates for a valid address when API key is present', async () => {
    // Arrange
    process.env.GOOGLE_MAPS_API_KEY = 'test-api-key';
    const mockApiResponse = {
      status: 'OK',
      results: [{
        geometry: {
          location: { lat: 34.0522, lng: -118.2437 },
        },
      }],
    };
    vi.mocked(fetch).mockResolvedValue({
      ok: true,
      json: async () => mockApiResponse,
    } as Response);

    // Act
    const result = await googleGeocodingService.geocode('Los Angeles, CA', mockLogger);

    // Assert
    expect(result).toEqual({ lat: 34.0522, lng: -118.2437 });
    expect(fetch).toHaveBeenCalledWith(expect.stringContaining('https://maps.googleapis.com/maps/api/geocode/json'));
    expect(mockLogger.info).toHaveBeenCalledWith(
      { address: 'Los Angeles, CA', result: { lat: 34.0522, lng: -118.2437 } },
      '[GoogleGeocodingService] Successfully geocoded address'
    );
  });

  it('should throw an error if GOOGLE_MAPS_API_KEY is not set', async () => {
    // Arrange
    delete process.env.GOOGLE_MAPS_API_KEY;

    // Act & Assert
    await expect(googleGeocodingService.geocode('Any Address', mockLogger))
      .rejects.toThrow('GOOGLE_MAPS_API_KEY is not set.');
    expect(mockLogger.error).toHaveBeenCalledWith('[GoogleGeocodingService] API key is missing.');
  });

  it('should return null if the API returns a status other than "OK"', async () => {
    // Arrange
    process.env.GOOGLE_MAPS_API_KEY = 'test-api-key';
    const mockApiResponse = { status: 'ZERO_RESULTS', results: [] };
    vi.mocked(fetch).mockResolvedValue({
      ok: true,
      json: async () => mockApiResponse,
    } as Response);

    // Act
    const result = await googleGeocodingService.geocode('Invalid Address', mockLogger);

    // Assert
    expect(result).toBeNull();
    expect(mockLogger.warn).toHaveBeenCalledWith(
      { address: 'Invalid Address', status: 'ZERO_RESULTS' },
      '[GoogleGeocodingService] Geocoding failed or returned no results.'
    );
  });

  it('should throw an error if the fetch response is not ok', async () => {
    // Arrange
    process.env.GOOGLE_MAPS_API_KEY = 'test-api-key';
    vi.mocked(fetch).mockResolvedValue({
      ok: false,
      status: 403,
    } as Response);

    // Act & Assert
    await expect(googleGeocodingService.geocode('Any Address', mockLogger))
      .rejects.toThrow('Google Maps API returned status 403');
    expect(mockLogger.error).toHaveBeenCalledWith(
      { err: expect.any(Error), address: 'Any Address' },
      '[GoogleGeocodingService] An error occurred while calling the Google Maps API.'
    );
  });

  it('should throw an error if the fetch call itself fails', async () => {
    // Arrange
    process.env.GOOGLE_MAPS_API_KEY = 'test-api-key';
    const networkError = new Error('Network request failed');
    vi.mocked(fetch).mockRejectedValue(networkError);

    // Act & Assert
    await expect(googleGeocodingService.geocode('Any Address', mockLogger))
      .rejects.toThrow(networkError);
    expect(mockLogger.error).toHaveBeenCalledWith(
      { err: networkError, address: 'Any Address' },
      '[GoogleGeocodingService] An error occurred while calling the Google Maps API.'
    );
  });
});