<?php
namespace App\Services;

class OpenStreetMapRoutesService
{
    public function computeWalkingRoute(array $points): array
    {
        $coords = $this->cleanCoordinates($points);
        if (count($coords) < 2) {
            throw new \RuntimeException('Add at least two route points.');
        }

        $config = $GLOBALS['config']['maps']['osm'] ?? [];
        $useOsrm = !empty($config['use_osrm_demo']);

        if ($useOsrm) {
            try {
                return $this->computeWithOsrm($coords, $config);
            } catch (\Throwable $e) {
                $fallback = $this->computeManualPolyline($coords);
                $fallback['message'] = 'OSRM demo route failed, so Pyrk used the manual point-to-point route. ' . $e->getMessage();
                return $fallback;
            }
        }

        return $this->computeManualPolyline($coords);
    }

    private function cleanCoordinates(array $points): array
    {
        $coords = [];
        foreach ($points as $point) {
            if (!isset($point['latitude'], $point['longitude'])) {
                continue;
            }
            $lat = (float)$point['latitude'];
            $lng = (float)$point['longitude'];
            if ($lat === 0.0 && $lng === 0.0) {
                continue;
            }
            $coords[] = ['lat' => $lat, 'lng' => $lng];
        }
        return $coords;
    }

    private function computeManualPolyline(array $coords): array
    {
        $distanceMeters = 0.0;
        for ($i = 1; $i < count($coords); $i++) {
            $distanceMeters += $this->haversineMeters($coords[$i - 1], $coords[$i]);
        }

        $distanceKm = round($distanceMeters / 1000, 2);
        // Conservative walking estimate: 5 km/hour.
        $durationMinutes = max(1, (int)round(($distanceKm / 5) * 60));

        return [
            'distance_km' => $distanceKm,
            'duration_minutes' => $durationMinutes,
            'geometry_geojson' => json_encode([
                'type' => 'LineString',
                'coordinates' => array_map(fn($c) => [$c['lng'], $c['lat']], $coords),
            ], JSON_UNESCAPED_SLASHES),
            'encoded_polyline' => '',
            'bounds' => $this->bounds($coords),
            'provider' => 'osm_manual',
            'message' => 'Route calculated with free OpenStreetMap manual line mode. Distance is approximate between your selected points.',
        ];
    }

    private function computeWithOsrm(array $coords, array $config): array
    {
        $baseUrl = rtrim((string)($config['osrm_url'] ?? 'https://router.project-osrm.org'), '/');
        $profile = preg_replace('/[^a-z]/', '', (string)($config['osrm_profile'] ?? 'foot')) ?: 'foot';
        $coordString = implode(';', array_map(fn($c) => $c['lng'] . ',' . $c['lat'], $coords));
        $url = $baseUrl . '/route/v1/' . $profile . '/' . $coordString . '?overview=full&geometries=geojson&steps=false';

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 12,
            CURLOPT_HTTPHEADER => ['Accept: application/json'],
        ]);
        $raw = curl_exec($ch);
        $http = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $err = curl_error($ch);
        curl_close($ch);

        if ($raw === false || $http < 200 || $http >= 300) {
            throw new \RuntimeException('OSRM HTTP error ' . $http . ($err ? ': ' . $err : ''));
        }
        $data = json_decode($raw, true);
        if (($data['code'] ?? '') !== 'Ok' || empty($data['routes'][0])) {
            throw new \RuntimeException('OSRM did not return a valid route.');
        }
        $route = $data['routes'][0];
        $geometry = $route['geometry'] ?? ['type' => 'LineString', 'coordinates' => []];
        $geoCoords = [];
        foreach (($geometry['coordinates'] ?? []) as $pair) {
            if (count($pair) >= 2) {
                $geoCoords[] = ['lng' => (float)$pair[0], 'lat' => (float)$pair[1]];
            }
        }

        return [
            'distance_km' => round(((float)$route['distance']) / 1000, 2),
            'duration_minutes' => max(1, (int)round(((float)$route['duration']) / 60)),
            'geometry_geojson' => json_encode($geometry, JSON_UNESCAPED_SLASHES),
            'encoded_polyline' => '',
            'bounds' => $this->bounds($geoCoords ?: $coords),
            'provider' => 'osrm_demo',
            'message' => 'Route calculated with OSRM demo routing. Use manual mode or a private OSRM/commercial service for production.',
        ];
    }

    private function bounds(array $coords): array
    {
        $lats = array_column($coords, 'lat');
        $lngs = array_column($coords, 'lng');
        return [
            'south' => min($lats),
            'west' => min($lngs),
            'north' => max($lats),
            'east' => max($lngs),
        ];
    }

    private function haversineMeters(array $a, array $b): float
    {
        $earth = 6371000;
        $lat1 = deg2rad((float)$a['lat']);
        $lat2 = deg2rad((float)$b['lat']);
        $dLat = deg2rad((float)$b['lat'] - (float)$a['lat']);
        $dLng = deg2rad((float)$b['lng'] - (float)$a['lng']);
        $h = sin($dLat / 2) ** 2 + cos($lat1) * cos($lat2) * sin($dLng / 2) ** 2;
        return 2 * $earth * atan2(sqrt($h), sqrt(1 - $h));
    }
}
