const { sequelize } = require("../models");
const { QueryTypes } = require("sequelize");
const logger = require("../utils/logger");

class LocationService {
  /**
   * Find users within a specified radius of coordinates
   * Uses the Haversine formula to calculate great-circle distance between two points
   *
   * @param {number} latitude - Center point latitude
   * @param {number} longitude - Center point longitude
   * @param {number} radiusMiles - Search radius in miles (default: 10)
   * @returns {Promise<Array>} Array of users with their distance from the center point
   */
  async findUsersInRadius(latitude, longitude, radiusMiles = 10) {
    if (!latitude || !longitude) {
      throw new Error("Latitude and longitude are required");
    }

    if (radiusMiles <= 0 || radiusMiles > 100) {
      throw new Error("Radius must be between 1 and 100 miles");
    }

    try {
      // Haversine formula:
      // distance = 3959 * acos(cos(radians(lat1)) * cos(radians(lat2))
      //            * cos(radians(lng2) - radians(lng1))
      //            + sin(radians(lat1)) * sin(radians(lat2)))
      // Note: 3959 is Earth's radius in miles
      const query = `
        SELECT * FROM (
          SELECT
            u.id,
            u.email,
            u."firstName",
            u."lastName",
            ua.latitude,
            ua.longitude,
            (3959 * acos(
              LEAST(1.0,
                cos(radians(:lat)) * cos(radians(ua.latitude))
                * cos(radians(ua.longitude) - radians(:lng))
                + sin(radians(:lat)) * sin(radians(ua.latitude))
              )
            )) AS distance
          FROM "Users" u
          INNER JOIN "UserAddresses" ua ON u.id = ua."userId"
          WHERE ua."isPrimary" = true
            AND ua.latitude IS NOT NULL
            AND ua.longitude IS NOT NULL
        ) AS user_distances
        WHERE distance < :radiusMiles
        ORDER BY distance ASC
      `;

      const users = await sequelize.query(query, {
        replacements: {
          lat: parseFloat(latitude),
          lng: parseFloat(longitude),
          radiusMiles: parseFloat(radiusMiles),
        },
        type: QueryTypes.SELECT,
      });

      return users.map((user) => ({
        id: user.id,
        email: user.email,
        firstName: user.firstName,
        lastName: user.lastName,
        latitude: parseFloat(user.latitude),
        longitude: parseFloat(user.longitude),
        distance: parseFloat(user.distance).toFixed(2), // Round to 2 decimal places
      }));
    } catch (error) {
      logger.error("Error finding users in radius", { error });
      throw new Error(`Failed to find users in radius: ${error.message}`);
    }
  }

  /**
   * Calculate distance between two points using Haversine formula
   *
   * @param {number} lat1 - First point latitude
   * @param {number} lon1 - First point longitude
   * @param {number} lat2 - Second point latitude
   * @param {number} lon2 - Second point longitude
   * @returns {number} Distance in miles
   */
  calculateDistance(lat1, lon1, lat2, lon2) {
    const R = 3959; // Earth's radius in miles
    const dLat = this.toRadians(lat2 - lat1);
    const dLon = this.toRadians(lon2 - lon1);

    const a =
      Math.sin(dLat / 2) * Math.sin(dLat / 2) +
      Math.cos(this.toRadians(lat1)) *
        Math.cos(this.toRadians(lat2)) *
        Math.sin(dLon / 2) *
        Math.sin(dLon / 2);

    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    const distance = R * c;

    return distance;
  }

  /**
   * Convert degrees to radians
   * @param {number} degrees
   * @returns {number} Radians
   */
  toRadians(degrees) {
    return degrees * (Math.PI / 180);
  }
}

module.exports = new LocationService();
