using System.Globalization;
using System.Text.Json;
using Microsoft.JSInterop;
using RutasDBUS.Modelos.Historial;

namespace RutasDBUS.Components.Pages;

public partial class PlanificadorRutas
{
    private const string ClaveHistorialLocal = "rutasdbus.planificador.routeHistory.v1";
    private const int MaxItemsHistorialLocal = 100;
    private const int IntervaloRefrescoHistorialCompartidoSegundos = 3;

    private readonly List<RegistroHistorialRuta> _historialLocal = new();
    private readonly List<RegistroHistorialRuta> _historialCompartido = new();
    private PeriodicTimer? _historialCompartidoTimer;
    private CancellationTokenSource? _historialCompartidoCts;
    private string _historialLocalSeleccionadoId = string.Empty;
    private string _historialCompartidoSeleccionadoId = string.Empty;
    private string _mensajeHistorial = string.Empty;
    private bool _mensajeHistorialEsError = false;
    private bool _mostrarHistorialRutas = false;
    private bool _refrescandoHistorialCompartido = false;
    private bool _omitirRegistroHistorialEnSiguienteCalculo = false;
    private string? _historialCompartidoPendienteEliminarId;
    private ConfiguracionLocalPlanificador? _configuracionTemporalPreviaHistorial;
    private Task<bool>? _tareaCargaHistorialLocal;
    private bool _historialLocalCargado = false;

    private async Task ToggleHistorialPanel()
    {
        if (_mostrarHistorialRutas)
        {
            _mostrarHistorialRutas = false;
            StopRefrescoHistorialCompartido();
            await RestaurarConfiguracionTemporalHistorialAsync();
            return;
        }

        if (!await AsegurarHistorialLocalCargadoAsync())
        {
            MostrarMensajeHistorial("No he podido cargar el historial personal. Intentalo de nuevo.", esError: true);
            return;
        }

        _mostrarHistorialRutas = true;
        await CargarHistorialCompartidoAsync();
        StartRefrescoHistorialCompartido();
    }

    private async Task<bool> AsegurarHistorialLocalCargadoAsync()
    {
        if (_historialLocalCargado)
            return true;

        _tareaCargaHistorialLocal ??= CargarHistorialLocalDesdeNavegadorAsync();
        var tareaCarga = _tareaCargaHistorialLocal;
        var cargado = await tareaCarga;

        if (!cargado && ReferenceEquals(_tareaCargaHistorialLocal, tareaCarga))
            _tareaCargaHistorialLocal = null;

        return cargado;
    }

    private async Task<bool> CargarHistorialLocalDesdeNavegadorAsync()
    {
        try
        {
            var json = await JS.InvokeAsync<string?>("rt.loadLocalValue", ClaveHistorialLocal);
            if (!string.IsNullOrWhiteSpace(json))
            {
                var items = JsonSerializer.Deserialize<List<RegistroHistorialRuta>>(json);
                if (items is null)
                    return false;

                _historialLocal.Clear();
                _historialLocal.AddRange(items
                    .Where(x => !string.IsNullOrWhiteSpace(x.Id))
                    .OrderByDescending(x => x.FechaActualizacionUtc)
                    .Take(MaxItemsHistorialLocal));
            }

            _historialLocalSeleccionadoId = _historialLocal.FirstOrDefault()?.Id ?? string.Empty;
            _historialLocalCargado = true;
            return true;
        }
        catch (JSException)
        {
            return false;
        }
        catch (JsonException)
        {
            return false;
        }
    }

    private async Task PersistirHistorialLocalAsync()
    {
        if (!_historialLocalCargado)
            return;

        try
        {
            var json = JsonSerializer.Serialize(_historialLocal);
            await JS.InvokeVoidAsync("rt.saveLocalValue", ClaveHistorialLocal, json);
        }
        catch (JSException)
        {
        }
    }

    private async Task CargarHistorialCompartidoAsync(bool mostrarError = true)
    {
        if (_refrescandoHistorialCompartido)
            return;

        _refrescandoHistorialCompartido = true;
        try
        {
            var items = await HistorialRutasCompartidoService.ObtenerAsync();
            _historialCompartido.Clear();
            _historialCompartido.AddRange(items);

            if (_historialCompartido.Count == 0)
            {
                _historialCompartidoSeleccionadoId = string.Empty;
                return;
            }

            if (!_historialCompartido.Any(x => x.Id == _historialCompartidoSeleccionadoId))
                _historialCompartidoSeleccionadoId = _historialCompartido[0].Id;
        }
        catch (Exception)
        {
            if (mostrarError)
                MostrarMensajeHistorial("No he podido cargar el historial compartido.", esError: true);
        }
        finally
        {
            _refrescandoHistorialCompartido = false;
        }
    }

    private void StartRefrescoHistorialCompartido()
    {
        if (_historialCompartidoTimer is not null)
            return;

        var cts = new CancellationTokenSource();
        var timer = new PeriodicTimer(TimeSpan.FromSeconds(IntervaloRefrescoHistorialCompartidoSegundos));
        _historialCompartidoCts = cts;
        _historialCompartidoTimer = timer;

        _ = Task.Run(async () =>
        {
            try
            {
                while (await timer.WaitForNextTickAsync(cts.Token))
                {
                    if (!_mostrarHistorialRutas)
                        continue;

                    await InvokeAsync(async () =>
                    {
                        await CargarHistorialCompartidoAsync(mostrarError: false);
                        StateHasChanged();
                    });
                }
            }
            catch
            {
                // Timer cancelled when the popup or component closes.
            }
        });
    }

    private void StopRefrescoHistorialCompartido()
    {
        _historialCompartidoCts?.Cancel();
        _historialCompartidoTimer?.Dispose();
        _historialCompartidoCts?.Dispose();

        _historialCompartidoTimer = null;
        _historialCompartidoCts = null;
    }

    private async Task RegistrarEscenarioActualEnHistorialLocalAsync()
    {
        if (!await AsegurarHistorialLocalCargadoAsync())
            return;

        var actual = ConstruirRegistroHistorialActual();
        if (actual is null)
            return;

        var clave = CalcularClaveEscenario(actual);
        var existente = _historialLocal.FirstOrDefault(x =>
            string.Equals(CalcularClaveEscenario(x), clave, StringComparison.OrdinalIgnoreCase));

        if (existente is null)
        {
            existente = actual;
            _historialLocal.Insert(0, existente);
        }
        else
        {
            existente.OrigenTexto = actual.OrigenTexto;
            existente.DestinoTexto = actual.DestinoTexto;
            existente.OrigenLatitud = actual.OrigenLatitud;
            existente.OrigenLongitud = actual.OrigenLongitud;
            existente.DestinoLatitud = actual.DestinoLatitud;
            existente.DestinoLongitud = actual.DestinoLongitud;
            existente.UsarHorariosTeoricos = actual.UsarHorariosTeoricos;
            existente.UsarHoraSimulada = actual.UsarHoraSimulada;
            existente.FechaSimulada = actual.FechaSimulada;
            existente.HoraSimulada = actual.HoraSimulada;
            existente.CriterioPrioritarioRuta = actual.CriterioPrioritarioRuta;
            existente.PreferenciaEtiquetaRuta = actual.PreferenciaEtiquetaRuta;
            existente.FactorEquilibradoAndarBus = actual.FactorEquilibradoAndarBus;
            existente.ActivarFiltroLineas = actual.ActivarFiltroLineas;
            existente.LineasPermitidas = actual.LineasPermitidas.ToList();
            existente.FechaActualizacionUtc = DateTime.UtcNow;

            _historialLocal.Remove(existente);
            _historialLocal.Insert(0, existente);
        }

        while (_historialLocal.Count > MaxItemsHistorialLocal)
            _historialLocal.RemoveAt(_historialLocal.Count - 1);

        _historialLocalSeleccionadoId = existente.Id;
        await PersistirHistorialLocalAsync();
    }

    private async Task CargarHistorialLocalSeleccionadoAsync()
    {
        var item = _historialLocal.FirstOrDefault(x => x.Id == _historialLocalSeleccionadoId);
        if (item is null)
        {
            MostrarMensajeHistorial("No encuentro esa combinacion en tu historial personal.", esError: true);
            return;
        }

        await AplicarRegistroHistorialAsync(item);
        MostrarMensajeHistorial("Combinacion personal cargada.");
    }

    private async Task CargarHistorialLocalAsync(string id)
    {
        _historialLocalSeleccionadoId = id;
        await CargarHistorialLocalSeleccionadoAsync();
    }

    private async Task CargarHistorialCompartidoSeleccionadoAsync()
    {
        var item = _historialCompartido.FirstOrDefault(x => x.Id == _historialCompartidoSeleccionadoId);
        if (item is null)
        {
            MostrarMensajeHistorial("No encuentro esa combinacion en el historial compartido.", esError: true);
            return;
        }

        await AplicarRegistroHistorialAsync(item);
        MostrarMensajeHistorial("Combinación compartida cargada.");
    }

    private async Task CargarHistorialCompartidoAsync(string id)
    {
        _historialCompartidoSeleccionadoId = id;
        await CargarHistorialCompartidoSeleccionadoAsync();
    }

    private async Task CompartirEscenarioActualAsync()
    {
        var actual = ConstruirRegistroHistorialActual()
            ?? _historialLocal.FirstOrDefault(x => x.Id == _historialLocalSeleccionadoId);

        if (actual is null)
        {
            MostrarMensajeHistorial("Primero necesitamos una combinacion valida para poder compartirla.", esError: true);
            return;
        }

        try
        {
            var guardado = await HistorialRutasCompartidoService.GuardarAsync(actual);
            await CargarHistorialCompartidoAsync();
            _historialCompartidoSeleccionadoId = guardado.Id;
            MostrarMensajeHistorial("Combinacion enviada al historial compartido.");
        }
        catch (Exception)
        {
            MostrarMensajeHistorial("No he podido guardar la combinacion en el historial compartido.", esError: true);
        }
    }

    private async Task CompartirHistorialLocalAsync(string id)
    {
        _historialLocalSeleccionadoId = id;
        var item = _historialLocal.FirstOrDefault(x => x.Id == id);
        if (item is null)
        {
            MostrarMensajeHistorial("No encuentro esa combinacion en tu historial personal.", esError: true);
            return;
        }

        try
        {
            var guardado = await HistorialRutasCompartidoService.GuardarAsync(item);
            await CargarHistorialCompartidoAsync();
            _historialCompartidoSeleccionadoId = guardado.Id;
            MostrarMensajeHistorial("Combinacion enviada al historial compartido.");
        }
        catch (Exception)
        {
            MostrarMensajeHistorial("No he podido guardar la combinacion en el historial compartido.", esError: true);
        }
    }

    private async Task EliminarHistorialLocalAsync(string id)
    {
        if (!await AsegurarHistorialLocalCargadoAsync())
            return;

        var item = _historialLocal.FirstOrDefault(x => x.Id == id);
        if (item is null)
            return;

        _historialLocal.Remove(item);

        if (_historialLocalSeleccionadoId == id)
            _historialLocalSeleccionadoId = _historialLocal.FirstOrDefault()?.Id ?? string.Empty;

        await PersistirHistorialLocalAsync();
        MostrarMensajeHistorial("Combinacion eliminada del historial personal.");
    }

    private void PedirConfirmacionEliminarHistorialCompartido(string id)
    {
        if (string.IsNullOrWhiteSpace(id))
            return;

        _historialCompartidoPendienteEliminarId = id;
    }

    private void CancelarConfirmacionEliminarHistorialCompartido()
    {
        _historialCompartidoPendienteEliminarId = null;
    }

    private async Task ConfirmarEliminarHistorialCompartidoAsync()
    {
        var id = _historialCompartidoPendienteEliminarId;
        _historialCompartidoPendienteEliminarId = null;

        if (string.IsNullOrWhiteSpace(id))
            return;

        await EliminarHistorialCompartidoAsync(id);
    }

    private async Task EliminarHistorialCompartidoAsync(string id)
    {
        try
        {
            var ok = await HistorialRutasCompartidoService.EliminarAsync(id);
            if (!ok)
            {
                MostrarMensajeHistorial("No he podido eliminar esa combinacion compartida.", esError: true);
                return;
            }

            await CargarHistorialCompartidoAsync();

            if (_historialCompartidoSeleccionadoId == id)
                _historialCompartidoSeleccionadoId = _historialCompartido.FirstOrDefault()?.Id ?? string.Empty;

            MostrarMensajeHistorial("Combinacion eliminada del historial compartido.");
        }
        catch (Exception)
        {
            MostrarMensajeHistorial("No he podido eliminar esa combinacion compartida.", esError: true);
        }
    }

    private async Task AplicarRegistroHistorialAsync(RegistroHistorialRuta item)
    {
        CapturarConfiguracionTemporalAntesDeHistorial();

        _omitirRegistroHistorialEnSiguienteCalculo = true;
        _textoBusquedaOrigen = item.OrigenTexto;
        _textoBusquedaDestino = item.DestinoTexto;
        _coordenadasOrigen = new[] { item.OrigenLatitud, item.OrigenLongitud };
        _coordenadasDestino = new[] { item.DestinoLatitud, item.DestinoLongitud };

        _usarHorariosTeoricos = item.UsarHorariosTeoricos;
        _usarHoraSimulada = item.UsarHoraSimulada;

        if (DateOnly.TryParse(item.FechaSimulada, out var fecha))
            _fechaSimulada = fecha;

        if (TimeOnly.TryParse(item.HoraSimulada, out var hora))
            _horaSimulada = hora;

        _criterioPrioritarioRuta = NormalizarCriterioRuta(item.CriterioPrioritarioRuta, item.PreferenciaEtiquetaRuta);
        SincronizarPreferenciaDesdeCriterioRuta();

        if (item.FactorEquilibradoAndarBus is { } factorEquilibrado && double.IsFinite(factorEquilibrado))
        {
            _factorEquilibradoAndarBus = Math.Max(0, factorEquilibrado);
            SincronizarTextosPenalizacionPie();

            if (_configuracionTemporalPreviaHistorial is not null)
                _configuracionTemporalPreviaHistorial.FactorEquilibradoAndarBus = _factorEquilibradoAndarBus;
        }

        _activarRangoLineas = item.ActivarFiltroLineas;
        _lineasPermitidas.Clear();
        foreach (var linea in NormalizarLineasHistorial(item.LineasPermitidas))
            _lineasPermitidas.Add(linea);
        _busFilteredCache.Clear();

        SincronizarSegundosPorParadaConHorarios();

        StopClock();
        if (_usarHoraSimulada)
            await RefrescarHoraUIAsync();
        else
            StartClock();

        await PersistirConfiguracionAsync();
        await CerrarTodasLasPreviewsAsync();
        await CerrarPopupsAsync();

        if (_mapa is not null)
        {
            try { await _mapa.Geometric.DisplayPolylinesFromArray.deleteConnectors(); } catch { }
            await ClearRoutesJsAsync();
        }

        await SincronizarMarcadoresOrigenDestinoJsAsync();
        await CalcularRutaCompletaAsync();
    }

    private void CapturarConfiguracionTemporalAntesDeHistorial()
    {
        if (_configuracionTemporalPreviaHistorial is not null)
            return;

        _configuracionTemporalPreviaHistorial = CrearConfiguracionLocal();
    }

    private async Task RestaurarConfiguracionTemporalHistorialAsync()
    {
        var configuracionPrevia = _configuracionTemporalPreviaHistorial;
        if (configuracionPrevia is null)
            return;

        _configuracionTemporalPreviaHistorial = null;
        AplicarConfiguracionLocal(configuracionPrevia);

        StopClock();
        if (_usarHoraSimulada)
            await RefrescarHoraUIAsync();
        else
            StartClock();

        await PersistirConfiguracionAsync();
    }

    private RegistroHistorialRuta? ConstruirRegistroHistorialActual()
    {
        if (_coordenadasOrigen is null || _coordenadasDestino is null)
            return null;

        var ahora = _ultimaHoraReferenciaRuta ?? AhoraLocal();

        return new RegistroHistorialRuta
        {
            Id = Guid.NewGuid().ToString("N"),
            OrigenTexto = ResolverTextoPuntoHistorial(_coordenadasOrigen),
            DestinoTexto = ResolverTextoPuntoHistorial(_coordenadasDestino),
            OrigenLatitud = _coordenadasOrigen[0],
            OrigenLongitud = _coordenadasOrigen[1],
            DestinoLatitud = _coordenadasDestino[0],
            DestinoLongitud = _coordenadasDestino[1],
            UsarHorariosTeoricos = _usarHorariosTeoricos,
            UsarHoraSimulada = _usarHoraSimulada,
            FechaSimulada = DateOnly.FromDateTime(ahora).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
            HoraSimulada = TimeOnly.FromDateTime(ahora).ToString("HH:mm:ss", CultureInfo.InvariantCulture),
            CriterioPrioritarioRuta = _criterioPrioritarioRuta,
            PreferenciaEtiquetaRuta = _preferenciaEtiquetaRuta,
            FactorEquilibradoAndarBus = double.IsFinite(_factorEquilibradoAndarBus)
                ? Math.Max(0, _factorEquilibradoAndarBus)
                : 12.0,
            ActivarFiltroLineas = _activarRangoLineas,
            LineasPermitidas = ObtenerLineasPermitidasSeleccionadas().ToList(),
            FechaCreacionUtc = DateTime.UtcNow,
            FechaActualizacionUtc = DateTime.UtcNow
        };
    }

    private static string? FormatearParadaHistorial(RutasDBUS.Modelos.RedBus.Parada? parada)
    {
        if (parada is null || string.IsNullOrWhiteSpace(parada.Codigo))
            return null;

        var nombre = string.IsNullOrWhiteSpace(parada.Nombre)
            ? parada.Codigo
            : parada.Nombre.Trim();

        return $"{nombre} ({CodigoParadaFormateado(parada.Codigo)})";
    }

    private string ResolverTextoPuntoHistorial(double[] coords)
    {
        var lat = coords[0];
        var lon = coords[1];

        var parada = RedBusServicio.BuscarParadaMasCercana(lat, lon);

        var textoParada = FormatearParadaHistorial(parada);
        if (!string.IsNullOrWhiteSpace(textoParada))
            return textoParada;

        return $"{lat.ToString("0.000000", CultureInfo.InvariantCulture)}, {lon.ToString("0.000000", CultureInfo.InvariantCulture)}";
    }

    private string TextoOpcionHistorial(RegistroHistorialRuta item)
    {
        var sello = item.FechaActualizacionUtc == default
            ? string.Empty
            : item.FechaActualizacionUtc.ToLocalTime().ToString("dd/MM HH:mm", CultureInfo.CurrentCulture);
        var horaReferencia = FormatearHoraReferenciaHistorial(item);
        var modo = FormatearModoHistorial(item);

        var textoBase = string.IsNullOrWhiteSpace(sello)
            ? $"{item.OrigenTexto} -> {item.DestinoTexto}"
            : $"{sello} - {item.OrigenTexto} -> {item.DestinoTexto}";

        var partes = new List<string> { textoBase };
        if (!string.IsNullOrWhiteSpace(horaReferencia))
            partes.Add($"HR: {horaReferencia}");
        if (!string.IsNullOrWhiteSpace(modo))
            partes.Add($"Modo: {modo}");
        return string.Join(" \u00b7 ", partes);

    }

    private static string FormatearModoHistorial(RegistroHistorialRuta item)
    {
        var criterio = item.CriterioPrioritarioRuta?.Trim();
        if (string.IsNullOrWhiteSpace(criterio))
        {
            var preferencia = item.PreferenciaEtiquetaRuta?.Trim() ?? string.Empty;
            return string.Equals(preferencia, PreferenciaRutaAndar, StringComparison.OrdinalIgnoreCase)
                ? "Andar"
                : preferencia;
        }

        if (string.Equals(criterio, CriterioRutaAndar, StringComparison.OrdinalIgnoreCase))
            return "Andar";
        if (string.Equals(criterio, CriterioRutaTiempo, StringComparison.OrdinalIgnoreCase))
            return "Tiempo";
        if (string.Equals(criterio, CriterioRutaEquilibrado, StringComparison.OrdinalIgnoreCase))
            return "Equilibrado";

        return criterio;
    }

    private static IReadOnlyList<int> NormalizarLineasHistorial(IEnumerable<int>? lineas)
        => (lineas ?? Array.Empty<int>())
            .Where(linea => linea > 0)
            .Distinct()
            .OrderBy(linea => linea)
            .ToList();

    private static string FormatearHoraReferenciaHistorial(RegistroHistorialRuta item)
    {
        if (string.IsNullOrWhiteSpace(item.HoraSimulada))
            return string.Empty;

        var formatosHora = new[] { "HH:mm:ss", "H:mm:ss", "HH:mm", "H:mm" };
        if (!TimeOnly.TryParseExact(
            item.HoraSimulada.Trim(),
            formatosHora,
            CultureInfo.InvariantCulture,
            DateTimeStyles.None,
            out var hora))
        {
            return string.Empty;
        }

        var horaTexto = hora.ToString("HH:mm", CultureInfo.InvariantCulture);
        if (string.IsNullOrWhiteSpace(item.FechaSimulada))
            return horaTexto;

        var formatosFecha = new[] { "yyyy-MM-dd", "dd/MM/yyyy", "d/M/yyyy", "dd/MM/yy", "d/M/yy" };
        return DateOnly.TryParseExact(
            item.FechaSimulada.Trim(),
            formatosFecha,
            CultureInfo.InvariantCulture,
            DateTimeStyles.None,
            out var fecha)
            ? $"{fecha.ToString("dd/MM", CultureInfo.InvariantCulture)} {horaTexto}"
            : $"{item.FechaSimulada.Trim()} {horaTexto}";
    }

    private void MostrarMensajeHistorial(string mensaje, bool esError = false)
    {
        _mensajeHistorial = mensaje;
        _mensajeHistorialEsError = esError;
    }

    private static string CalcularClaveEscenario(RegistroHistorialRuta item)
    {
        var origenLat = Math.Round(item.OrigenLatitud, 6).ToString("0.000000", CultureInfo.InvariantCulture);
        var origenLon = Math.Round(item.OrigenLongitud, 6).ToString("0.000000", CultureInfo.InvariantCulture);
        var destinoLat = Math.Round(item.DestinoLatitud, 6).ToString("0.000000", CultureInfo.InvariantCulture);
        var destinoLon = Math.Round(item.DestinoLongitud, 6).ToString("0.000000", CultureInfo.InvariantCulture);
        var fecha = item.UsarHoraSimulada ? item.FechaSimulada?.Trim() ?? string.Empty : string.Empty;
        var hora = item.UsarHoraSimulada ? item.HoraSimulada?.Trim() ?? string.Empty : string.Empty;
        var criterio = NormalizarCriterioRuta(item.CriterioPrioritarioRuta, item.PreferenciaEtiquetaRuta);
        var factorEquilibrado = item.FactorEquilibradoAndarBus is { } factor && double.IsFinite(factor)
            ? Math.Max(0, factor).ToString("0.######", CultureInfo.InvariantCulture)
            : string.Empty;
        var lineas = item.ActivarFiltroLineas
            ? string.Join(",", NormalizarLineasHistorial(item.LineasPermitidas))
            : string.Empty;

        return string.Join("|",
            origenLat,
            origenLon,
            destinoLat,
            destinoLon,
            item.UsarHorariosTeoricos ? "1" : "0",
            item.UsarHoraSimulada ? "1" : "0",
            fecha,
            hora,
            criterio,
            factorEquilibrado,
            item.ActivarFiltroLineas ? "1" : "0",
            lineas);
    }
}
