using System;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;

namespace RutasDBUS.Servicios.Caminata;

/// <summary>
/// Implementación del cliente OSRM para calcular rutas a pie.
/// </summary>
public class ClienteOsrm : IClienteOsrm
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl;

    /// <summary>
    /// Inicializa una nueva instancia de la clase ClienteOsrm.
    /// </summary>
    public ClienteOsrm(HttpClient httpClient, IConfiguration configuration)
    {
        _httpClient = httpClient;

        _baseUrl = configuration["Caminata:Osrm:BaseUrl"] ?? "";
        _baseUrl = _baseUrl.Trim();

        if (string.IsNullOrWhiteSpace(_baseUrl))
            throw new InvalidOperationException("Falta la configuración 'Caminata:Osrm:BaseUrl' en appsettings.json.");

        _baseUrl = _baseUrl.TrimEnd('/');
    }

    /// <inheritdoc />
    public async Task<(double[,]? Linea, double DistanciaMetros, double DuracionSegundos)> ObtenerRutaAPieAsync(
        double latitudDesde,
        double longitudDesde,
        double latitudHasta,
        double longitudHasta)
    {
        // OSRM: /route/v1/walking/{lon1},{lat1};{lon2},{lat2}?overview=full&geometries=geojson&alternatives=false
        var url =
            $"{_baseUrl}/walking/" +
            $"{longitudDesde.ToString(CultureInfo.InvariantCulture)}," +
            $"{latitudDesde.ToString(CultureInfo.InvariantCulture)};" +
            $"{longitudHasta.ToString(CultureInfo.InvariantCulture)}," +
            $"{latitudHasta.ToString(CultureInfo.InvariantCulture)}" +
            "?overview=full&geometries=geojson&alternatives=false";

        RespuestaOsrm? respuesta;
        try
        {
            respuesta = await _httpClient.GetFromJsonAsync<RespuestaOsrm>(url);
        }
        catch
        {
            return (null, 0, 0);
        }

        var ruta = respuesta?.Rutas?.FirstOrDefault();
        var coords = ruta?.Geometria?.Coordenadas;

        if (ruta is null || coords is null || coords.Count == 0)
            return (null, 0, 0);

        var linea = new double[coords.Count, 2];

        for (int i = 0; i < coords.Count; i++)
        {
            // GeoJSON: [lon, lat]
            double lon = coords[i][0];
            double lat = coords[i][1];

            linea[i, 0] = lat;
            linea[i, 1] = lon;
        }

        return (
            AnclarExtremosLinea(linea, latitudDesde, longitudDesde, latitudHasta, longitudHasta),
            ruta.Distancia,
            ruta.Duracion);
    }

    /// <summary>
    /// Fuerza que la geometria empiece y termine exactamente en los puntos solicitados.
    /// </summary>
    private static double[,] AnclarExtremosLinea(
        double[,] linea,
        double latitudDesde,
        double longitudDesde,
        double latitudHasta,
        double longitudHasta)
    {
        var puntos = linea.GetLength(0);
        if (puntos <= 0)
        {
            return new double[,]
            {
                { latitudDesde, longitudDesde },
                { latitudHasta, longitudHasta }
            };
        }

        if (puntos == 1)
        {
            return new double[,]
            {
                { latitudDesde, longitudDesde },
                { latitudHasta, longitudHasta }
            };
        }

        linea[0, 0] = latitudDesde;
        linea[0, 1] = longitudDesde;
        linea[puntos - 1, 0] = latitudHasta;
        linea[puntos - 1, 1] = longitudHasta;
        return linea;
    }

    // =========================
    // Modelos de respuesta OSRM
    // =========================

    private sealed class RespuestaOsrm
    {
        [JsonPropertyName("routes")]
        public System.Collections.Generic.List<RutaOsrm>? Rutas { get; set; }
    }

    private sealed class RutaOsrm
    {
        [JsonPropertyName("distance")]
        public double Distancia { get; set; }

        [JsonPropertyName("duration")]
        public double Duracion { get; set; }

        [JsonPropertyName("geometry")]
        public GeometriaOsrm? Geometria { get; set; }
    }

    private sealed class GeometriaOsrm
    {
        [JsonPropertyName("type")]
        public string? Tipo { get; set; }

        [JsonPropertyName("coordinates")]
        public System.Collections.Generic.List<System.Collections.Generic.List<double>>? Coordenadas { get; set; }
    }
}
