using System;
using System.Collections.Generic;
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;

public class ClienteValhalla : IClienteValhalla
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl;

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

        _baseUrl = configuracion["Caminata:Valhalla:BaseUrl"]
            ?? throw new InvalidOperationException(
                "Falta la configuración 'Caminata:Valhalla:BaseUrl' en appsettings.json.");

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

    public async Task<(double[,]? Linea, double DistanciaMetros, double DuracionSegundos)> ObtenerRutaAsync(
        double latitudDesde,
        double longitudDesde,
        double latitudHasta,
        double longitudHasta,
        string costing = "pedestrian")
    {
        var cuerpo = new SolicitudValhalla
        {
            Ubicaciones = new List<LocalizacionValhalla>
            {
                new() { Latitud = latitudDesde, Longitud = longitudDesde },
                new() { Latitud = latitudHasta, Longitud = longitudHasta }
            },
            PerfilCoste = string.IsNullOrWhiteSpace(costing) ? "pedestrian" : costing,
            FormatoGeometria = "polyline6",
            OpcionesIndicaciones = new OpcionesIndicacionesValhalla
            {
                Idioma = "es-ES",
                Unidades = "kilometers"
            }
        };

        HttpResponseMessage respuesta;
        try
        {
            respuesta = await _httpClient.PostAsJsonAsync($"{_baseUrl}/route", cuerpo);
        }
        catch
        {
            return (null, 0, 0);
        }

        if (!respuesta.IsSuccessStatusCode)
            return (null, 0, 0);

        RespuestaRutaValhalla? data;
        try
        {
            data = await respuesta.Content.ReadFromJsonAsync<RespuestaRutaValhalla>();
        }
        catch
        {
            return (null, 0, 0);
        }

        var viaje = data?.Viaje;
        if (viaje is null || viaje.Tramos is null || viaje.Tramos.Count == 0)
            return (null, 0, 0);

        var forma = viaje.Tramos[0]?.Forma;
        if (string.IsNullOrWhiteSpace(forma))
            return (null, 0, 0);

        var coords = DecodificarPolyline6(forma);
        if (coords.Count == 0)
            return (null, 0, 0);

        var linea = new double[coords.Count, 2];
        for (int i = 0; i < coords.Count; i++)
        {
            linea[i, 0] = coords[i].Latitud;
            linea[i, 1] = coords[i].Longitud;
        }

        // summary.length depende de units
        double longitud = viaje.Resumen?.Longitud ?? 0.0;
        string unidades = (viaje.Unidades ?? "").Trim().ToLowerInvariant();

        double distanciaMetros = unidades switch
        {
            "miles" => longitud * 1609.344,
            _ => longitud * 1000.0 // "kilometers" o desconocido -> km
        };

        double duracionSegundos = viaje.Resumen?.Tiempo ?? 0.0;

        return (
            AnclarExtremosLinea(linea, latitudDesde, longitudDesde, latitudHasta, longitudHasta),
            distanciaMetros,
            duracionSegundos);
    }

    // =======================
    // Modelos request/response
    // =======================

    private sealed class SolicitudValhalla
    {
        [JsonPropertyName("locations")]
        public List<LocalizacionValhalla> Ubicaciones { get; set; } = new();

        [JsonPropertyName("costing")]
        public string PerfilCoste { get; set; } = "pedestrian";

        [JsonPropertyName("shape_format")]
        public string FormatoGeometria { get; set; } = "polyline6";

        [JsonPropertyName("directions_options")]
        public OpcionesIndicacionesValhalla OpcionesIndicaciones { get; set; } = new();
    }

    private sealed class LocalizacionValhalla
    {
        [JsonPropertyName("lat")]
        public double Latitud { get; set; }

        [JsonPropertyName("lon")]
        public double Longitud { get; set; }
    }

    private sealed class OpcionesIndicacionesValhalla
    {
        [JsonPropertyName("language")]
        public string Idioma { get; set; } = "es-ES";

        [JsonPropertyName("units")]
        public string Unidades { get; set; } = "kilometers";
    }

    private sealed class RespuestaRutaValhalla
    {
        [JsonPropertyName("trip")]
        public ViajeValhalla? Viaje { get; set; }
    }

    private sealed class ViajeValhalla
    {
        [JsonPropertyName("summary")]
        public ResumenValhalla? Resumen { get; set; }

        [JsonPropertyName("legs")]
        public List<TramoValhalla?> Tramos { get; set; } = new();

        [JsonPropertyName("units")]
        public string? Unidades { get; set; } // "kilometers" / "miles"
    }

    private sealed class ResumenValhalla
    {
        [JsonPropertyName("length")]
        public double Longitud { get; set; } // en km si units=kilometers

        [JsonPropertyName("time")]
        public double Tiempo { get; set; } // segundos
    }

    private sealed class TramoValhalla
    {
        [JsonPropertyName("shape")]
        public string? Forma { get; set; } // polyline6
    }

    /// <summary>
    /// 
    /// </summary>
    private readonly record struct Coordenada(double Latitud, double Longitud);

    /// <summary>
    /// Decodifica polyline6 (factor 1e6).
    /// </summary>
    private static List<Coordenada> DecodificarPolyline6(string encoded)
    {
        var coords = new List<Coordenada>();
        if (string.IsNullOrEmpty(encoded)) return coords;

        int index = 0;
        int lat = 0, lon = 0;

        try
        {
            while (index < encoded.Length)
            {
                lat += LeerSiguienteValor(encoded, ref index);
                lon += LeerSiguienteValor(encoded, ref index);

                coords.Add(new Coordenada(lat / 1e6, lon / 1e6));
            }
        }
        catch
        {
            // si llega corrupto o incompleto, devolvemos lo que haya (o vacío)
            if (coords.Count == 0) return new List<Coordenada>();
        }

        return coords;
    }

    /// <summary>
    /// Lee siguiente valor.
    /// </summary>
    private static int LeerSiguienteValor(string encoded, ref int index)
    {
        int result = 0;
        int shift = 0;
        int b;

        do
        {
            if (index >= encoded.Length) throw new InvalidOperationException("Polyline incompleta.");
            b = encoded[index++] - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        }
        while (b >= 0x20);

        // zigzag decode
        return (result & 1) != 0 ? ~(result >> 1) : (result >> 1);
    }

    /// <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;
    }
}
