import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:path_provider/path_provider.dart';

import '../modelos/emisora.dart';
import '../modelos/preset_ecualizador.dart';
import '../servicios/servicio_audio.dart';
import '../servicios/servicio_ecualizador.dart';
import '../servicios/servicio_favoritos.dart';
import '../servicios/servicio_radio.dart';
import '../servicios/servicio_timer.dart';

/// Estado global de la app con ChangeNotifier (Provider).
class EstadoRadio extends ChangeNotifier {
  EstadoRadio({
    ServicioAudio? audio,
    ServicioFavoritos? favoritos,
    ServicioRadio? radio,
    ServicioEcualizador? servicioEcualizador,
    Future<File> Function()? resolverArchivoCustom,
    bool iniciarAutomaticamente = true,
  })  : audio = audio ?? ServicioAudio(),
        favoritos = favoritos ?? ServicioFavoritos(),
        radio = radio ?? ServicioRadio(),
        servicioEcualizador = servicioEcualizador ?? ServicioEcualizador(),
        _resolverArchivoCustom = resolverArchivoCustom {
    timer = ServicioTimer(this.audio);
    _escucharErroresReproduccion();
    if (iniciarAutomaticamente) {
      _initFuture = _init();
    }
  }

  final ServicioAudio audio;
  final ServicioFavoritos favoritos;
  final ServicioRadio radio;
  final ServicioEcualizador servicioEcualizador;
  final Future<File> Function()? _resolverArchivoCustom;

  late final ServicioTimer timer;
  StreamSubscription<EstadoReproduccion>? _suscripcionEstadoAudio;
  Future<void>? _initFuture;
  int _revisionReproduccion = 0;

  // Errores de reproducción → SnackBar.
  final _errorController = StreamController<String>.broadcast();
  Stream<String> get errorStream => _errorController.stream;

  List<Emisora> _populares = [];
  List<Emisora> _tendencias = [];
  List<Emisora> _resultadosBusqueda = [];
  List<Emisora> _emisorasCercanas = [];
  List<Emisora> _listaFavoritos = [];
  List<Emisora> _emisorasCustom = [];

  // Presets EQ guardados por uuid de emisora.
  final Map<String, PresetEcualizador> _presetsEmisoraMap = {};
  PresetEcualizador _presetPrincipal = PresetEcualizador.flat;
  PresetEcualizador _presetActual = PresetEcualizador.flat;

  bool _cargandoPopulares = false;
  bool _cargandoBusqueda = false;
  bool _cargandoMasBusqueda = false;
  bool _hayMasBusqueda = true;
  bool _cargandoCercanas = false;
  String? _paisCercanoDetectado;
  String? _errorCercanas;
  int _offsetBusqueda = 0;
  String? _ultimoNombreBusqueda;
  String? _ultimoPaisBusqueda;
  String? _ultimoIdiomaBusqueda;
  String? _ultimoTagBusqueda;
  String? _errorCarga;

  List<Emisora> get populares => _populares;
  List<Emisora> get tendencias => _tendencias;
  List<Emisora> get resultadosBusqueda => _resultadosBusqueda;
  List<Emisora> get emisorasCercanas => _emisorasCercanas;
  List<Emisora> get listaFavoritos => _listaFavoritos;
  List<Emisora> get emisorasCustom => _emisorasCustom;
  bool get cargandoPopulares => _cargandoPopulares;
  bool get cargandoBusqueda => _cargandoBusqueda;
  bool get cargandoMasBusqueda => _cargandoMasBusqueda;
  bool get hayMasBusqueda => _hayMasBusqueda;
  bool get cargandoCercanas => _cargandoCercanas;
  String? get paisCercanoDetectado => _paisCercanoDetectado;
  String? get errorCercanas => _errorCercanas;
  String? get error => _errorCarga;
  Emisora? get emisoraActual => audio.emisoraActual;
  Stream<EstadoReproduccion> get estadoStream => audio.estadoStream;
  PresetEcualizador get presetEcualizador => _presetActual;
  PresetEcualizador get presetPrincipalEcualizador => _presetPrincipal;
  bool get ecualizadorDisponible => audio.ecualizadorDisponible;

  bool get emisoraActualEsFavorita {
    final actual = emisoraActual;
    if (actual == null) return false;
    return _listaFavoritos.any((e) => e.uuid == actual.uuid);
  }

  bool get emisoraActualTienePresetPropio {
    final actual = emisoraActual;
    if (actual == null) return false;
    return tienePresetEcualizadorPorEmisora(actual.uuid);
  }

  /// Lista principal (home): custom + populares, sin duplicados.
  List<Emisora> get emisorasInicio {
    final mapa = <String, Emisora>{};
    for (final emisora in _emisorasCustom) {
      mapa[emisora.uuid] = emisora;
    }
    for (final emisora in _populares) {
      mapa.putIfAbsent(emisora.uuid, () => emisora);
    }
    return mapa.values.toList();
  }

  Future<void> inicializar() {
    _initFuture ??= _init();
    return _initFuture!;
  }

  Future<void> _init() async {
    await _cargarEcualizadorPersistido();
    await Future.wait([
      cargarPopulares(),
      cargarFavoritos(),
      _cargarEmisorasCustom(),
    ]);
  }

  /// Escucha el stream de estado del audio y gestiona errores de reproducción.
  void _escucharErroresReproduccion() {
    _suscripcionEstadoAudio = audio.estadoStream.listen((estado) {
      if (estado == EstadoReproduccion.error && timer.activo) {
        unawaited(timer.cancelar());
      }
      notifyListeners();
    });
  }

  Future<void> _cargarEcualizadorPersistido() async {
    try {
      final config = await servicioEcualizador.cargar();
      _presetPrincipal = config.principal;
      _presetActual = config.principal;
      _presetsEmisoraMap
        ..clear()
        ..addAll(config.porEmisora);
      await audio.aplicarPreset(_presetPrincipal);
    } catch (_) {
      _presetPrincipal = PresetEcualizador.flat;
      _presetActual = PresetEcualizador.flat;
      _presetsEmisoraMap.clear();
    }
  }

  Future<void> cargarPopulares() async {
    _cargandoPopulares = true;
    _errorCarga = null;
    notifyListeners();
    try {
      final results = await Future.wait([
        radio.obtenerPopulares(limit: 30),
        radio.obtenerTendencias(limit: 20),
      ]);
      _populares = results[0];
      _tendencias = results[1];
    } catch (_) {
      _errorCarga = 'Sin conexión a la API de radio';
    } finally {
      _cargandoPopulares = false;
      notifyListeners();
    }
  }

  Future<void> cargarFavoritos() async {
    _listaFavoritos = await favoritos.obtenerTodos();
    notifyListeners();
  }

  static const int _tamanoPaginaBusqueda = 30;
  static const int _maxResultadosBusquedaEnMemoria = 180;

  Future<void> buscar({
    String? nombre,
    String? pais,
    String? idioma,
    String? tag,
  }) async {
    _ultimoNombreBusqueda = nombre;
    _ultimoPaisBusqueda = pais;
    _ultimoIdiomaBusqueda = idioma;
    _ultimoTagBusqueda = tag;
    _offsetBusqueda = 0;
    _hayMasBusqueda = true;
    _cargandoBusqueda = true;
    _resultadosBusqueda = [];
    notifyListeners();
    try {
      final pagina = await radio.buscar(
        nombre: nombre,
        pais: pais,
        idioma: idioma,
        tag: tag,
        limit: _tamanoPaginaBusqueda,
        offset: _offsetBusqueda,
      );
      _resultadosBusqueda = pagina;
      _offsetBusqueda = pagina.length;
      _hayMasBusqueda = pagina.length == _tamanoPaginaBusqueda;
    } catch (_) {
      _errorController.add('Error en la busqueda. Comprueba tu conexion.');
    } finally {
      _cargandoBusqueda = false;
      notifyListeners();
    }
  }

  Future<void> cargarMasBusqueda() async {
    if (_cargandoBusqueda || _cargandoMasBusqueda || !_hayMasBusqueda) return;
    _cargandoMasBusqueda = true;
    notifyListeners();
    try {
      final pagina = await radio.buscar(
        nombre: _ultimoNombreBusqueda,
        pais: _ultimoPaisBusqueda,
        idioma: _ultimoIdiomaBusqueda,
        tag: _ultimoTagBusqueda,
        limit: _tamanoPaginaBusqueda,
        offset: _offsetBusqueda,
      );
      final porUuid = <String, Emisora>{
        for (final emisora in _resultadosBusqueda) emisora.uuid: emisora,
      };
      for (final emisora in pagina) {
        porUuid[emisora.uuid] = emisora;
      }
      var nuevaLista = porUuid.values.toList();
      if (nuevaLista.length > _maxResultadosBusquedaEnMemoria) {
        nuevaLista = nuevaLista.sublist(
          nuevaLista.length - _maxResultadosBusquedaEnMemoria,
        );
      }
      _resultadosBusqueda = nuevaLista;
      _offsetBusqueda += pagina.length;
      _hayMasBusqueda = pagina.length == _tamanoPaginaBusqueda;
    } catch (_) {
      _errorController.add('No se pudieron cargar mas emisoras.');
    } finally {
      _cargandoMasBusqueda = false;
      notifyListeners();
    }
  }

  Future<void> cargarEmisorasCercanas() async {
    _cargandoCercanas = true;
    _errorCercanas = null;
    notifyListeners();
    try {
      var pais = PlatformDispatcher.instance.locale.countryCode;
      final servicioActivo = await Geolocator.isLocationServiceEnabled();
      if (servicioActivo) {
        var permiso = await Geolocator.checkPermission();
        if (permiso == LocationPermission.denied) {
          permiso = await Geolocator.requestPermission();
        }
        if (permiso == LocationPermission.always ||
            permiso == LocationPermission.whileInUse) {
          final posicion = await Geolocator.getCurrentPosition(
            locationSettings: const LocationSettings(
              accuracy: LocationAccuracy.low,
              timeLimit: Duration(seconds: 8),
            ),
          );
          final marcas = await placemarkFromCoordinates(
            posicion.latitude,
            posicion.longitude,
          );
          if (marcas.isNotEmpty) {
            pais = marcas.first.isoCountryCode ?? pais;
          }
        }
      }

      if (pais == null || pais.isEmpty) {
        throw Exception('No se pudo detectar tu region');
      }
      _paisCercanoDetectado = pais;
      _emisorasCercanas = await radio.buscar(pais: pais, limit: 30);
    } catch (_) {
      _errorCercanas = 'No pudimos detectar emisoras cercanas. Usa filtros por pais.';
      _emisorasCercanas = [];
    } finally {
      _cargandoCercanas = false;
      notifyListeners();
    }
  }

  Future<void> reproducir(Emisora emisora) async {
    final revision = ++_revisionReproduccion;
    try {
      await audio.reproducir(emisora);
      if (revision != _revisionReproduccion) return;
      unawaited(radio.registrarClick(emisora.uuid));
      await _aplicarPresetActivo(_presetParaEmisora(emisora.uuid));
      if (revision != _revisionReproduccion) return;
      notifyListeners();
    } catch (e) {
      if (revision != _revisionReproduccion) return;
      if (timer.activo) {
        unawaited(timer.cancelar());
      }
      final mensajeError = e.toString().replaceFirst('Exception: ', '');
      _errorController.add(
        mensajeError.isNotEmpty && mensajeError != 'Exception'
            ? mensajeError
            : 'No se puede reproducir "${emisora.nombre}"',
      );
      notifyListeners();
    }
  }

  Future<void> togglePlay() async {
    await audio.togglePlay();
    notifyListeners();
  }

  Future<bool> toggleFavorito(Emisora emisora) async {
    final esFav = await favoritos.toggleFavorito(emisora);
    if (!esFav) {
      await deshabilitarPresetEcualizadorPorEmisora(emisora.uuid, notificar: false);
    }
    await cargarFavoritos();
    return esFav;
  }

  Future<bool> esFavorito(String uuid) => favoritos.esFavorito(uuid);

  // ── Ecualizador ───────────────────────────────────────────────────────────

  bool tienePresetEcualizadorPorEmisora(String uuid) =>
      _presetsEmisoraMap.containsKey(uuid);

  PresetEcualizador? presetEcualizadorPorEmisora(String uuid) =>
      _presetsEmisoraMap[uuid];

  PresetEcualizador _presetParaEmisora(String uuid) =>
      _presetsEmisoraMap[uuid] ?? _presetPrincipal;

  Future<void> _aplicarPresetActivo(PresetEcualizador preset) async {
    _presetActual = preset;
    await audio.aplicarPreset(preset);
  }

  Future<void> cambiarPresetPrincipalEcualizador(
    PresetEcualizador preset, {
    bool notificar = true,
  }) async {
    _presetPrincipal = preset;
    await servicioEcualizador.guardarPrincipal(preset);

    final actual = emisoraActual;
    final puedeAplicarAhora =
        actual == null || !_presetsEmisoraMap.containsKey(actual.uuid);
    if (puedeAplicarAhora) {
      await _aplicarPresetActivo(preset);
    }

    if (notificar) notifyListeners();
  }

  Future<void> guardarPresetEcualizadorPorEmisora(
    String uuid,
    PresetEcualizador preset, {
    bool notificar = true,
  }) async {
    _presetsEmisoraMap[uuid] = preset;
    await servicioEcualizador.guardarPorEmisora(uuid, preset);
    if (emisoraActual?.uuid == uuid) {
      await _aplicarPresetActivo(preset);
    }
    if (notificar) notifyListeners();
  }

  Future<void> habilitarPresetEcualizadorPorEmisora(
    String uuid, {
    PresetEcualizador? base,
    bool notificar = true,
  }) async {
    final presetBase = base ?? _presetsEmisoraMap[uuid] ?? _presetPrincipal;
    await guardarPresetEcualizadorPorEmisora(
      uuid,
      presetBase,
      notificar: notificar,
    );
  }

  Future<void> deshabilitarPresetEcualizadorPorEmisora(
    String uuid, {
    bool notificar = true,
  }) async {
    _presetsEmisoraMap.remove(uuid);
    await servicioEcualizador.eliminarPorEmisora(uuid);
    if (emisoraActual?.uuid == uuid) {
      await _aplicarPresetActivo(_presetPrincipal);
    }
    if (notificar) notifyListeners();
  }

  Future<void> cambiarModoEcualizadorEmisoraActual({required bool usarPropio}) async {
    final actual = emisoraActual;
    if (actual == null) return;
    if (usarPropio) {
      await habilitarPresetEcualizadorPorEmisora(actual.uuid);
    } else {
      await deshabilitarPresetEcualizadorPorEmisora(actual.uuid);
    }
  }

  Future<void> cambiarPresetEcualizador(
    PresetEcualizador preset, {
    bool guardarPorEmisora = true,
  }) async {
    final actual = emisoraActual;
    final usarPresetPropio = guardarPorEmisora &&
        actual != null &&
        _presetsEmisoraMap.containsKey(actual.uuid);

    if (usarPresetPropio) {
      await guardarPresetEcualizadorPorEmisora(actual.uuid, preset);
      return;
    }
    await cambiarPresetPrincipalEcualizador(preset);
  }

  Future<void> cambiarBandaEcualizador(int index, double db) async {
    final bandas = List<double>.from(_presetActual.bandas);
    if (index < 0 || index >= bandas.length) return;

    bandas[index] = db;
    final modificado = PresetEcualizador(
      nombre: 'Personalizado',
      bandas: bandas,
    );
    await cambiarPresetEcualizador(modificado);
  }

  // ── Emisoras personalizadas ───────────────────────────────────────────────

  Future<File> _archivoCustom() async {
    if (_resolverArchivoCustom != null) {
      return _resolverArchivoCustom();
    }
    final dir = await getApplicationDocumentsDirectory();
    return File('${dir.path}/emisoras_custom.json');
  }

  Future<void> _cargarEmisorasCustom() async {
    try {
      final archivo = await _archivoCustom();
      if (!await archivo.exists()) {
        _emisorasCustom = [];
        notifyListeners();
        return;
      }

      final data = jsonDecode(await archivo.readAsString()) as List;
      _emisorasCustom = data
          .map((e) => Emisora.fromMap(Map<String, dynamic>.from(e as Map)))
          .toList();
    } catch (_) {
      _emisorasCustom = [];
    }
    notifyListeners();
  }

  Future<void> _guardarEmisorasCustom() async {
    final archivo = await _archivoCustom();
    await archivo.writeAsString(
      jsonEncode(_emisorasCustom.map((e) => e.toMap()).toList()),
    );
  }

  Future<void> agregarEmisoraCustom(Emisora emisora) async {
    _emisorasCustom.removeWhere((e) => e.uuid == emisora.uuid);
    _emisorasCustom.add(emisora);
    await _guardarEmisorasCustom();
    notifyListeners();
  }

  // Compatibilidad con el nombre histórico (typo original).
  Future<void> agregarEmitoraCustom(Emisora emisora) =>
      agregarEmisoraCustom(emisora);

  Future<void> eliminarEmisoraCustom(String uuid) async {
    _emisorasCustom.removeWhere((e) => e.uuid == uuid);
    await _guardarEmisorasCustom();
    notifyListeners();
  }

  // Compatibilidad con el nombre histórico (typo original).
  Future<void> eliminarEmitoraCustom(String uuid) => eliminarEmisoraCustom(uuid);

  // ── Export / Import ───────────────────────────────────────────────────────

  /// Genera el JSON de toda la configuración.
  Future<Map<String, dynamic>> exportarConfig() async {
    final favs = await favoritos.obtenerTodos();
    return {
      'version': 1,
      'exportedAt': DateTime.now().toIso8601String(),
      'favoritos': favs.map((e) => e.toMap()).toList(),
      'emisorasCustom': _emisorasCustom.map((e) => e.toMap()).toList(),
      'presetPrincipalEcualizador': _presetPrincipal.toJson(),
      'presetsEcualizador': _presetsEmisoraMap.map(
        (uuid, preset) => MapEntry(uuid, preset.toJson()),
      ),
    };
  }

  /// Importa configuración desde un JSON exportado previamente.
  Future<void> importarConfig(Map<String, dynamic> data) async {
    final version = data['version'] as int? ?? 1;
    if (version != 1) throw Exception('Versión de configuración no compatible');

    final favRaw = data['favoritos'] as List? ?? [];
    for (final raw in favRaw) {
      final emisora = Emisora.fromMap(Map<String, dynamic>.from(raw as Map));
      await favoritos.agregar(emisora);
    }

    final customRaw = data['emisorasCustom'] as List? ?? [];
    _emisorasCustom = customRaw
        .map((e) => Emisora.fromMap(Map<String, dynamic>.from(e as Map)))
        .toList();
    await _guardarEmisorasCustom();

    final principalRaw = data['presetPrincipalEcualizador'];
    if (principalRaw is Map) {
      _presetPrincipal = PresetEcualizador.desdeJson(
        Map<String, dynamic>.from(principalRaw),
      );
    } else {
      _presetPrincipal = PresetEcualizador.flat;
    }

    final presetsRaw = data['presetsEcualizador'] as Map? ?? {};
    _presetsEmisoraMap
      ..clear()
      ..addAll(
        presetsRaw.map<String, PresetEcualizador>(
          (uuid, presetJson) => MapEntry(
            uuid as String,
            PresetEcualizador.desdeJson(
              Map<String, dynamic>.from(presetJson as Map),
            ),
          ),
        ),
      );

    await servicioEcualizador.guardarConfiguracion(
      ConfiguracionEcualizador(
        principal: _presetPrincipal,
        porEmisora: _presetsEmisoraMap,
      ),
    );

    final actual = emisoraActual;
    final presetActivo =
        actual == null ? _presetPrincipal : _presetParaEmisora(actual.uuid);
    await _aplicarPresetActivo(presetActivo);

    await cargarFavoritos();
    notifyListeners();
  }

  // ── Timer ─────────────────────────────────────────────────────────────────

  void iniciarTimer(int minutos) {
    timer.iniciar(minutos);
    notifyListeners();
  }

  void cancelarTimer() {
    unawaited(timer.cancelar());
    notifyListeners();
  }

  @override
  void dispose() {
    _suscripcionEstadoAudio?.cancel();
    _errorController.close();
    audio.dispose();
    timer.dispose();
    super.dispose();
  }
}
