/**
 * Controller per Geocoding usando OpenRouteService
 * Documentazione: https://openrouteservice.org/dev/#/api-docs
 */

const https = require('https');

// Configurazione OpenRouteService
const ORS_BASE = 'https://api.openrouteservice.org';
const ORS_API_KEY = process.env.ORS_API_KEY || 'YOUR_API_KEY_HERE';

/**
 * Helper per fare richieste HTTPS a OpenRouteService
 */
const makeRequest = (url, method = 'GET', body = null) => {
  return new Promise((resolve, reject) => {
    const urlObj = new URL(url);

    const options = {
      hostname: urlObj.hostname,
      path: urlObj.pathname + urlObj.search,
      method,
      headers: {
        Authorization: ORS_API_KEY,
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
    };

    const req = https.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => (data += chunk));
      res.on('end', () => {
        try {
          const parsed = JSON.parse(data);
          if (res.statusCode >= 400) {
            reject(new Error(parsed.error?.message || `HTTP ${res.statusCode}`));
          } else {
            resolve(parsed);
          }
        } catch (e) {
          reject(new Error('Errore parsing risposta'));
        }
      });
    });

    req.on('error', reject);
    req.setTimeout(15000, () => {
      req.destroy();
      reject(new Error('Timeout richiesta'));
    });

    if (body) {
      req.write(JSON.stringify(body));
    }
    req.end();
  });
};

/**
 * @desc    Autocomplete città (ORS Geocode Autocomplete)
 * @route   GET /api/geo/autocomplete
 */
const autocomplete = async (req, res) => {
  try {
    const { q, limit = 5, lang = 'it', country = 'IT' } = req.query;

    if (!q || q.length < 2) {
      return res.status(400).json({
        success: false,
        message: 'Query deve essere almeno 2 caratteri',
      });
    }

    const params = new URLSearchParams({
      text: q,
      size: limit,
      lang,
      'boundary.country': country,
      layers: 'locality,county,region', // Solo città/comuni
    });

    const url = `${ORS_BASE}/geocode/autocomplete?${params}`;
    const data = await makeRequest(url);

    const results = data.features.map((feature) => ({
      id: feature.properties.id,
      city: feature.properties.name,
      locality: feature.properties.locality,
      county: feature.properties.county,
      region: feature.properties.region,
      country: feature.properties.country,
      postalCode: feature.properties.postalcode,
      coordinates: {
        lat: feature.geometry.coordinates[1],
        lng: feature.geometry.coordinates[0],
      },
      displayName: feature.properties.label,
      type: feature.properties.layer,
      confidence: feature.properties.confidence,
    }));

    res.status(200).json({
      success: true,
      count: results.length,
      data: results,
    });
  } catch (error) {
    console.error('Errore autocomplete:', error);
    res.status(500).json({
      success: false,
      message: 'Errore durante la ricerca',
      error: error.message,
    });
  }
};

/**
 * @desc    Geocoding - indirizzo a coordinate (ORS Geocode Search)
 * @route   GET /api/geo/geocode
 */
const geocode = async (req, res) => {
  try {
    const { address, city, country = 'IT', limit = 5, lang = 'it' } = req.query;

    const searchQuery = [address, city].filter(Boolean).join(', ');

    if (!searchQuery) {
      return res.status(400).json({
        success: false,
        message: 'Fornisci un indirizzo o città da cercare',
      });
    }

    const params = new URLSearchParams({
      text: searchQuery,
      size: limit,
      lang,
        'boundary.country': country,
    });

    const url = `${ORS_BASE}/geocode/search?${params}`;
    const data = await makeRequest(url);

    if (!data.features || data.features.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'Nessun risultato trovato',
      });
    }

    const results = data.features.map((feature) => ({
      id: feature.properties.id,
      displayName: feature.properties.label,
      name: feature.properties.name,
      street: feature.properties.street,
      houseNumber: feature.properties.housenumber,
      city: feature.properties.locality || feature.properties.county,
      county: feature.properties.county,
      region: feature.properties.region,
      country: feature.properties.country,
      postalCode: feature.properties.postalcode,
      coordinates: {
        lat: feature.geometry.coordinates[1],
        lng: feature.geometry.coordinates[0],
      },
      type: feature.properties.layer,
      confidence: feature.properties.confidence,
    }));

    res.status(200).json({
      success: true,
      count: results.length,
      data: results,
    });
  } catch (error) {
    console.error('Errore geocoding:', error);
    res.status(500).json({
      success: false,
      message: 'Errore durante il geocoding',
      error: error.message,
    });
  }
};

/**
 * @desc    Reverse geocoding - coordinate a indirizzo (ORS Reverse)
 * @route   GET /api/geo/reverse
 */
const reverseGeocode = async (req, res) => {
  try {
    const { lat, lng, lang = 'it' } = req.query;

    if (!lat || !lng) {
      return res.status(400).json({
        success: false,
        message: 'Coordinate lat e lng richieste',
      });
    }

    const params = new URLSearchParams({
      'point.lat': lat,
      'point.lon': lng,
      lang,
      size: '1',
        layers: 'address,street,locality',
    });

    const url = `${ORS_BASE}/geocode/reverse?${params}`;
    const data = await makeRequest(url);

    if (!data.features || data.features.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'Nessun risultato trovato',
      });
    }

    const feature = data.features[0];

    const result = {
      displayName: feature.properties.label,
      name: feature.properties.name,
      street: feature.properties.street,
      houseNumber: feature.properties.housenumber,
      city: feature.properties.locality || feature.properties.county,
      county: feature.properties.county,
      region: feature.properties.region,
      country: feature.properties.country,
      postalCode: feature.properties.postalcode,
      coordinates: {
        lat: parseFloat(lat),
        lng: parseFloat(lng),
      },
      distance: feature.properties.distance, // distanza dal punto esatto
    };

    res.status(200).json({
      success: true,
      data: result,
    });
  } catch (error) {
    console.error('Errore reverse geocoding:', error);
    res.status(500).json({
      success: false,
      message: 'Errore durante il reverse geocoding',
      error: error.message,
    });
  }
};

/**
 * @desc    Calcola percorso tra due o più punti (ORS Directions)
 * @route   POST /api/geo/route
 * @body    { coordinates: [[lng,lat], [lng,lat], ...], profile: 'driving-car' }
 */
const getRoute = async (req, res) => {
  try {
    const {
      startLat,
      startLng,
      endLat,
      endLng,
      waypoints, // formato: "lat1,lng1;lat2,lng2;..."
      profile = 'driving-car', // driving-car, driving-hgv, cycling-regular, foot-walking
      language = 'it',
      units = 'km',
    } = req.query;

    if (!startLat || !startLng || !endLat || !endLng) {
      return res.status(400).json({
        success: false,
        message: 'Coordinate di partenza e arrivo richieste',
      });
    }

    // Costruisci array coordinate [lng, lat] (formato GeoJSON)
    const coordinates = [[parseFloat(startLng), parseFloat(startLat)]];

    if (waypoints) {
      const waypointsList = waypoints.split(';');
      waypointsList.forEach((wp) => {
        const [lat, lng] = wp.split(',').map(parseFloat);
        coordinates.push([lng, lat]);
      });
    }

    coordinates.push([parseFloat(endLng), parseFloat(endLat)]);

    // Richiesta POST a ORS Directions
    const url = `${ORS_BASE}/v2/directions/${profile}`;

    const body = {
      coordinates,
      language,
      units,
      geometry: true,
      instructions: true,
      maneuvers: true,
    };

    const data = await makeRequest(url, 'POST', body);

    if (!data.routes || data.routes.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'Impossibile calcolare il percorso',
      });
    }

    const route = data.routes[0];
    const summary = route.summary;

    const result = {
      distance: Math.round(summary.distance * 10) / 10, // km
      duration: Math.round(summary.duration / 60), // minuti
      durationFormatted: formatDuration(summary.duration),
      bbox: data.bbox, // Bounding box
      geometry: route.geometry, // Polyline encoded
      segments: route.segments.map((segment) => ({
        distance: Math.round(segment.distance * 10) / 10,
        duration: Math.round(segment.duration / 60),
        steps: segment.steps.map((step) => ({
          instruction: step.instruction,
          name: step.name,
          distance: Math.round(step.distance * 100) / 100,
          duration: Math.round(step.duration / 60),
          type: step.type,
          maneuver: step.maneuver,
        })),
      })),
    };

    res.status(200).json({
      success: true,
      data: result,
    });
  } catch (error) {
    console.error('Errore calcolo percorso:', error);
    res.status(500).json({
      success: false,
      message: 'Errore durante il calcolo del percorso',
      error: error.message,
    });
  }
};

/**
 * @desc    Calcola matrice distanze tra più punti (ORS Matrix)
 * @route   POST /api/geo/matrix
 */
const getMatrix = async (req, res) => {
  try {
    const { locations, profile = 'driving-car' } = req.body;

    if (!locations || locations.length < 2) {
      return res.status(400).json({
        success: false,
        message: 'Almeno 2 location richieste',
      });
    }

    // Formato locations: [[lng, lat], [lng, lat], ...]
    const url = `${ORS_BASE}/v2/matrix/${profile}`;

    const body = {
      locations,
      metrics: ['distance', 'duration'],
      units: 'km',
    };

    const data = await makeRequest(url, 'POST', body);

    const result = {
      distances: data.distances, // Matrice distanze in km
      durations: data.durations, // Matrice durate in secondi
      sources: data.sources,
      destinations: data.destinations,
    };

    res.status(200).json({
      success: true,
      data: result,
    });
  } catch (error) {
    console.error('Errore calcolo matrice:', error);
    res.status(500).json({
      success: false,
      message: 'Errore durante il calcolo della matrice',
      error: error.message,
    });
  }
};

/**
 * @desc    Suggerisci città intermedie su un percorso
 * @route   GET /api/geo/suggest-waypoints
 */
const suggestWaypoints = async (req, res) => {
  try {
    const { startLat, startLng, endLat, endLng, count = 3 } = req.query;

    if (!startLat || !startLng || !endLat || !endLng) {
      return res.status(400).json({
        success: false,
        message: 'Coordinate di partenza e arrivo richieste',
      });
    }

    // Prima ottieni il percorso
    const routeUrl = `${ORS_BASE}/v2/directions/driving-car`;
    const routeBody = {
      coordinates: [
        [parseFloat(startLng), parseFloat(startLat)],
        [parseFloat(endLng), parseFloat(endLat)],
      ],
      geometry: true,
    };

    const routeData = await makeRequest(routeUrl, 'POST', routeBody);

    if (!routeData.routes || routeData.routes.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'Impossibile calcolare il percorso',
      });
    }

    // Decodifica polyline per ottenere punti
    const geometry = routeData.routes[0].geometry;
    const decodedPoints = decodePolyline(geometry);

    // Seleziona punti equidistanti lungo il percorso
    const totalPoints = decodedPoints.length;
    const step = Math.floor(totalPoints / (parseInt(count) + 1));

    const sampledPoints = [];
    for (let i = 1; i <= count; i++) {
      const index = Math.min(step * i, totalPoints - 1);
      sampledPoints.push(decodedPoints[index]);
    }

    // Fai reverse geocoding per ogni punto
    const cities = [];
    const seenCities = new Set();

    for (const point of sampledPoints) {
      try {
        const params = new URLSearchParams({
          'point.lat': point[1],
          'point.lon': point[0],
          lang: 'it',
          size: '1',
            layers: 'locality,county',
        });

        const reverseUrl = `${ORS_BASE}/geocode/reverse?${params}`;
        const data = await makeRequest(reverseUrl);

        if (data.features && data.features.length > 0) {
          const feature = data.features[0];
          const cityName = feature.properties.locality || feature.properties.county;

          if (cityName && !seenCities.has(cityName.toLowerCase())) {
            seenCities.add(cityName.toLowerCase());
            cities.push({
              city: cityName,
              county: feature.properties.county,
              region: feature.properties.region,
              coordinates: {
                lat: point[1],
                lng: point[0],
              },
              displayName: feature.properties.label,
            });
          }
        }
      } catch (e) {
        console.log('Errore reverse per punto:', e.message);
      }
    }

    res.status(200).json({
      success: true,
      count: cities.length,
      data: cities,
    });
  } catch (error) {
    console.error('Errore suggerimento waypoints:', error);
    res.status(500).json({
      success: false,
      message: 'Errore durante il suggerimento delle tappe',
      error: error.message,
    });
  }
};

/**
 * @desc    Cerca città italiane (ottimizzato)
 * @route   GET /api/geo/cities/it
 */
const searchItalianCities = async (req, res) => {
  try {
    const { q, limit = 10, region } = req.query;

    if (!q || q.length < 2) {
      return res.status(400).json({
        success: false,
        message: 'Query deve essere almeno 2 caratteri',
      });
    }

    const params = new URLSearchParams({
      text: q,
      size: limit,
      lang: 'it',
      'boundary.country': 'IT',
        layers: 'locality,county',
    });

    // Filtro opzionale per regione
    if (region) {
      params.append('region', region);
    }

    const url = `${ORS_BASE}/geocode/search?${params}`;
    const data = await makeRequest(url);

    const results = data.features
      .filter((f) => f.properties.locality || f.properties.county)
      .map((feature) => ({
        city: feature.properties.locality || feature.properties.name,
        county: feature.properties.county,
        region: feature.properties.region,
        postalCode: feature.properties.postalcode,
        coordinates: {
          lat: feature.geometry.coordinates[1],
          lng: feature.geometry.coordinates[0],
        },
        displayName: `${feature.properties.locality || feature.properties.name}, ${feature.properties.region}`,
        confidence: feature.properties.confidence,
      }));

    // Rimuovi duplicati
    const unique = results.filter(
      (v, i, a) => a.findIndex((t) => t.city?.toLowerCase() === v.city?.toLowerCase()) === i
    );

    res.status(200).json({
      success: true,
      count: unique.length,
      data: unique,
    });
  } catch (error) {
    console.error('Errore ricerca città italiane:', error);
    res.status(500).json({
      success: false,
      message: 'Errore durante la ricerca',
      error: error.message,
    });
  }
};

/**
 * @desc    Calcola distanza e durata tra due punti (semplificato)
 * @route   GET /api/geo/distance
 */
const getDistance = async (req, res) => {
  try {
    const { startLat, startLng, endLat, endLng, profile = 'driving-car' } = req.query;

    if (!startLat || !startLng || !endLat || !endLng) {
      return res.status(400).json({
        success: false,
        message: 'Tutte le coordinate sono richieste',
      });
    }

    const url = `${ORS_BASE}/v2/directions/${profile}`;

    const body = {
      coordinates: [
        [parseFloat(startLng), parseFloat(startLat)],
        [parseFloat(endLng), parseFloat(endLat)],
      ],
      geometry: false,
      instructions: false,
    };

    const data = await makeRequest(url, 'POST', body);

    if (!data.routes || data.routes.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'Impossibile calcolare la distanza',
      });
    }

    const summary = data.routes[0].summary;

    res.status(200).json({
      success: true,
      data: {
        distance: Math.round(summary.distance * 10) / 10, // km
        duration: Math.round(summary.duration / 60), // minuti
        durationFormatted: formatDuration(summary.duration),
        profile,
      },
    });
  } catch (error) {
    console.error('Errore calcolo distanza:', error);
    res.status(500).json({
      success: false,
      message: 'Errore durante il calcolo della distanza',
      error: error.message,
    });
  }
};

/**
 * @desc    Ottieni isocrone (aree raggiungibili in X minuti)
 * @route   GET /api/geo/isochrone
 */
const getIsochrone = async (req, res) => {
  try {
    const { lat, lng, minutes = 30, profile = 'driving-car' } = req.query;

    if (!lat || !lng) {
      return res.status(400).json({
        success: false,
        message: 'Coordinate richieste',
      });
    }

    const url = `${ORS_BASE}/v2/isochrones/${profile}`;

    const body = {
      locations: [[parseFloat(lng), parseFloat(lat)]],
      range: [parseInt(minutes) * 60], // secondi
      range_type: 'time',
    };

    const data = await makeRequest(url, 'POST', body);

    res.status(200).json({
      success: true,
      data: {
        type: 'FeatureCollection',
        features: data.features,
        center: { lat: parseFloat(lat), lng: parseFloat(lng) },
        minutes: parseInt(minutes),
      },
    });
  } catch (error) {
    console.error('Errore calcolo isocrone:', error);
    res.status(500).json({
      success: false,
      message: 'Errore durante il calcolo isocrone',
      error: error.message,
    });
  }
};

// ============================================
// HELPER FUNCTIONS
// ============================================

/**
 * Formatta durata in formato leggibile
 */
const formatDuration = (seconds) => {
  const hours = Math.floor(seconds / 3600);
  const minutes = Math.round((seconds % 3600) / 60);

  if (hours === 0) {
    return `${minutes} min`;
  } else if (minutes === 0) {
    return `${hours} h`;
  } else {
    return `${hours} h ${minutes} min`;
  }
};

/**
 * Decodifica polyline encoded (formato Google/ORS)
 */
const decodePolyline = (encoded) => {
  const points = [];
  let index = 0;
  let lat = 0;
  let lng = 0;

  while (index < encoded.length) {
    let b;
    let shift = 0;
    let result = 0;

    do {
      b = encoded.charCodeAt(index++) - 63;
      result |= (b & 0x1f) << shift;
      shift += 5;
    } while (b >= 0x20);

    const dlat = result & 1 ? ~(result >> 1) : result >> 1;
    lat += dlat;

    shift = 0;
    result = 0;

    do {
      b = encoded.charCodeAt(index++) - 63;
      result |= (b & 0x1f) << shift;
      shift += 5;
    } while (b >= 0x20);

    const dlng = result & 1 ? ~(result >> 1) : result >> 1;
    lng += dlng;

    points.push([lng / 1e5, lat / 1e5]); // [lng, lat] formato GeoJSON
  }

  return points;
};

module.exports = {
  autocomplete,
  geocode,
  reverseGeocode,
  getRoute,
  getMatrix,
  suggestWaypoints,
  searchItalianCities,
  getDistance,
  getIsochrone,
};
