// Mock dependencies before requiring the service
jest.mock('../../../models', () => ({
  sequelize: {
    query: jest.fn(),
  },
}));

jest.mock('sequelize', () => ({
  QueryTypes: {
    SELECT: 'SELECT',
  },
}));

jest.mock('../../../utils/logger', () => ({
  info: jest.fn(),
  error: jest.fn(),
  warn: jest.fn(),
}));

const { sequelize } = require('../../../models');
const locationService = require('../../../services/locationService');

describe('LocationService', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('findUsersInRadius', () => {
    it('should find users within specified radius', async () => {
      const mockUsers = [
        { id: 'user-1', email: 'user1@test.com', firstName: 'User', lastName: 'One', latitude: '37.7749', longitude: '-122.4194', distance: '1.5' },
        { id: 'user-2', email: 'user2@test.com', firstName: 'User', lastName: 'Two', latitude: '37.7849', longitude: '-122.4094', distance: '2.3' },
      ];

      sequelize.query.mockResolvedValue(mockUsers);

      const result = await locationService.findUsersInRadius(37.7749, -122.4194, 10);

      expect(result).toHaveLength(2);
      expect(result[0]).toMatchObject({
        id: 'user-1',
        email: 'user1@test.com',
        firstName: 'User',
        lastName: 'One',
      });
      expect(parseFloat(result[0].distance)).toBeCloseTo(1.5, 1);
    });

    it('should use default radius of 10 miles', async () => {
      sequelize.query.mockResolvedValue([]);

      await locationService.findUsersInRadius(37.7749, -122.4194);

      expect(sequelize.query).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({
          replacements: expect.objectContaining({
            radiusMiles: 10,
          }),
        })
      );
    });

    it('should throw error when latitude is missing', async () => {
      await expect(locationService.findUsersInRadius(null, -122.4194, 10))
        .rejects.toThrow('Latitude and longitude are required');
    });

    it('should throw error when longitude is missing', async () => {
      await expect(locationService.findUsersInRadius(37.7749, null, 10))
        .rejects.toThrow('Latitude and longitude are required');
    });

    it('should throw error when radius is zero', async () => {
      await expect(locationService.findUsersInRadius(37.7749, -122.4194, 0))
        .rejects.toThrow('Radius must be between 1 and 100 miles');
    });

    it('should throw error when radius is negative', async () => {
      await expect(locationService.findUsersInRadius(37.7749, -122.4194, -5))
        .rejects.toThrow('Radius must be between 1 and 100 miles');
    });

    it('should throw error when radius exceeds 100 miles', async () => {
      await expect(locationService.findUsersInRadius(37.7749, -122.4194, 150))
        .rejects.toThrow('Radius must be between 1 and 100 miles');
    });

    it('should handle database errors', async () => {
      sequelize.query.mockRejectedValue(new Error('Database error'));

      await expect(locationService.findUsersInRadius(37.7749, -122.4194, 10))
        .rejects.toThrow('Failed to find users in radius');
    });

    it('should return empty array when no users found', async () => {
      sequelize.query.mockResolvedValue([]);

      const result = await locationService.findUsersInRadius(37.7749, -122.4194, 10);

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

    it('should format distance to 2 decimal places', async () => {
      const mockUsers = [
        { id: 'user-1', email: 'user1@test.com', firstName: 'User', lastName: 'One', latitude: '37.7749', longitude: '-122.4194', distance: '1.23456789' },
      ];

      sequelize.query.mockResolvedValue(mockUsers);

      const result = await locationService.findUsersInRadius(37.7749, -122.4194, 10);

      expect(result[0].distance).toBe('1.23');
    });
  });

  describe('calculateDistance', () => {
    it('should calculate distance between two points', () => {
      // San Francisco to Los Angeles: approximately 347 miles
      const distance = locationService.calculateDistance(
        37.7749, -122.4194, // San Francisco
        34.0522, -118.2437  // Los Angeles
      );

      expect(distance).toBeGreaterThan(340);
      expect(distance).toBeLessThan(360);
    });

    it('should return 0 for same coordinates', () => {
      const distance = locationService.calculateDistance(
        37.7749, -122.4194,
        37.7749, -122.4194
      );

      expect(distance).toBe(0);
    });

    it('should calculate short distances accurately', () => {
      // Two points about 1 mile apart
      const distance = locationService.calculateDistance(
        37.7749, -122.4194,
        37.7893, -122.4094
      );

      expect(distance).toBeGreaterThan(0.5);
      expect(distance).toBeLessThan(2);
    });

    it('should handle negative coordinates', () => {
      // Sydney, Australia to Melbourne, Australia
      const distance = locationService.calculateDistance(
        -33.8688, 151.2093, // Sydney
        -37.8136, 144.9631  // Melbourne
      );

      expect(distance).toBeGreaterThan(400);
      expect(distance).toBeLessThan(500);
    });

    it('should handle crossing the prime meridian', () => {
      // London to Paris
      const distance = locationService.calculateDistance(
        51.5074, -0.1278, // London
        48.8566, 2.3522   // Paris
      );

      expect(distance).toBeGreaterThan(200);
      expect(distance).toBeLessThan(250);
    });
  });

  describe('toRadians', () => {
    it('should convert degrees to radians', () => {
      expect(locationService.toRadians(0)).toBe(0);
      expect(locationService.toRadians(180)).toBeCloseTo(Math.PI, 5);
      expect(locationService.toRadians(90)).toBeCloseTo(Math.PI / 2, 5);
      expect(locationService.toRadians(360)).toBeCloseTo(2 * Math.PI, 5);
    });

    it('should handle negative degrees', () => {
      expect(locationService.toRadians(-90)).toBeCloseTo(-Math.PI / 2, 5);
    });
  });
});
