// src/services/aiAnalysisService.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as aiApiClient from './aiApiClient';
import { AiAnalysisService } from './aiAnalysisService';
import { createMockFlyerItem } from '../tests/utils/mockFactories';

// Mock the dependencies
vi.mock('./aiApiClient');
vi.mock('./logger.client', () => ({
  logger: {
    info: vi.fn(),
    error: vi.fn(),
    warn: vi.fn(),
    debug: vi.fn(),
  },
}));

describe('AiAnalysisService', () => {
  let service: AiAnalysisService;

  beforeEach(() => {
    vi.clearAllMocks();
    service = new AiAnalysisService();
  });

  describe('getQuickInsights', () => {
    it('should return insights string on success', async () => {
      const mockResponse = 'Quick insights result';
      vi.mocked(aiApiClient.getQuickInsights).mockResolvedValue({
        json: () => Promise.resolve(mockResponse),
      } as Response);

      const result = await service.getQuickInsights([]);
      expect(result).toBe(mockResponse);
    });
  });

  describe('getDeepDiveAnalysis', () => {
    it('should return analysis string on success', async () => {
      const mockResponse = 'Deep dive result';
      vi.mocked(aiApiClient.getDeepDiveAnalysis).mockResolvedValue({
        json: () => Promise.resolve(mockResponse),
      } as Response);

      const result = await service.getDeepDiveAnalysis([]);
      expect(result).toBe(mockResponse);
    });
  });

  describe('searchWeb', () => {
    it('should return a grounded response on success', async () => {
      const mockResponse = {
        text: 'Search results',
        sources: [{ web: { uri: 'https://example.com', title: 'Example' } }],
      };
      vi.mocked(aiApiClient.searchWeb).mockResolvedValue({
        json: () => Promise.resolve(mockResponse),
      } as Response);

      const result = await service.searchWeb([createMockFlyerItem({ item: 'test' })]);

      expect(result.text).toBe('Search results');
      expect(result.sources).toEqual([{ uri: 'https://example.com', title: 'Example' }]);
    });

    it('should handle responses with missing or empty sources array', async () => {
      const mockResponse = { text: 'Search results', sources: null };
      vi.mocked(aiApiClient.searchWeb).mockResolvedValue({
        json: () => Promise.resolve(mockResponse),
      } as Response);

      const result = await service.searchWeb([createMockFlyerItem({ item: 'test' })]);

      expect(result.text).toBe('Search results');
      expect(result.sources).toEqual([]);
    });

    it('should handle source objects without a "web" property', async () => {
      const mockResponse = {
        text: 'Search results',
        sources: [{ some_other_property: 'value' }],
      };
      vi.mocked(aiApiClient.searchWeb).mockResolvedValue({
        json: () => Promise.resolve(mockResponse),
      } as Response);

      const result = await service.searchWeb([createMockFlyerItem({ item: 'test' })]);

      expect(result.sources).toEqual([{ uri: '', title: 'Untitled' }]);
    });

    it('should re-throw an error if the API call fails', async () => {
      const apiError = new Error('API is down');
      vi.mocked(aiApiClient.searchWeb).mockRejectedValue(apiError);

      await expect(service.searchWeb([createMockFlyerItem({ item: 'test' })])).rejects.toThrow(
        apiError,
      );
    });
  });

  describe('planTripWithMaps', () => {
    const mockGeolocation = {
      getCurrentPosition: vi.fn(),
    };

    beforeEach(() => {
      // Mock the global navigator object
      Object.defineProperty(global, 'navigator', {
        value: {
          geolocation: mockGeolocation,
        },
        writable: true,
      });
    });

    afterEach(() => {
      // Clean up the global mock
      vi.stubGlobal('navigator', undefined);
    });

    it('should call the API with the fetched location on success', async () => {
      const mockCoords = { latitude: 45, longitude: -75 };
      mockGeolocation.getCurrentPosition.mockImplementationOnce((success) => {
        success({ coords: mockCoords });
      });

      vi.mocked(aiApiClient.planTripWithMaps).mockResolvedValue({
        json: () => Promise.resolve({ text: 'Trip planned!', sources: [] }),
      } as Response);

      const result = await service.planTripWithMaps([], undefined);

      expect(mockGeolocation.getCurrentPosition).toHaveBeenCalledTimes(1);
      expect(aiApiClient.planTripWithMaps).toHaveBeenCalledWith([], undefined, mockCoords);
      expect(result.text).toBe('Trip planned!');
    });

    it('should reject if geolocation is not supported', async () => {
      // Undefine geolocation for this test
      Object.defineProperty(global, 'navigator', {
        value: {
          geolocation: undefined,
        },
        writable: true,
      });

      await expect(service.planTripWithMaps([], undefined)).rejects.toThrow(
        'Geolocation is not supported by your browser.',
      );
      expect(aiApiClient.planTripWithMaps).not.toHaveBeenCalled();
    });

    it('should reject if the user denies geolocation permission', async () => {
      // Create a mock object that conforms to the GeolocationPositionError interface.
      const permissionError = {
        code: 1, // PERMISSION_DENIED
        message: 'User denied Geolocation',
      };
      mockGeolocation.getCurrentPosition.mockImplementationOnce((_, error) => {
        if (error) {
          error(permissionError as GeolocationPositionError);
        }
      });

      await expect(service.planTripWithMaps([], undefined)).rejects.toEqual(permissionError);
      expect(aiApiClient.planTripWithMaps).not.toHaveBeenCalled();
    });
  });

  describe('compareWatchedItemPrices', () => {
    it('should return grounded response on success', async () => {
      const mockResponse = { text: 'Comparison', sources: [] };
      vi.mocked(aiApiClient.compareWatchedItemPrices).mockResolvedValue({
        json: () => Promise.resolve(mockResponse),
      } as Response);

      const result = await service.compareWatchedItemPrices([]);
      expect(result.text).toBe('Comparison');
    });

    it('should re-throw an error if the API call fails', async () => {
      const apiError = new Error('API is down');
      vi.mocked(aiApiClient.compareWatchedItemPrices).mockRejectedValue(apiError);

      await expect(service.compareWatchedItemPrices([])).rejects.toThrow(apiError);
    });
  });

  describe('generateImageFromText', () => {
    it('should return image string on success', async () => {
      const mockResponse = 'base64image';
      vi.mocked(aiApiClient.generateImageFromText).mockResolvedValue({
        json: () => Promise.resolve(mockResponse),
      } as Response);

      const result = await service.generateImageFromText('prompt');
      expect(result).toBe(mockResponse);
    });

    it('should re-throw an error if the API call fails', async () => {
      const apiError = new Error('API is down');
      vi.mocked(aiApiClient.generateImageFromText).mockRejectedValue(apiError);

      await expect(service.generateImageFromText('a prompt')).rejects.toThrow(apiError);
    });
  });
});
