import 'dart:async';
import 'dart:developer' as developer;

import 'package:audio_service/audio_service.dart';
import 'package:just_audio/just_audio.dart';

import '../modelos/emisora.dart';
import '../modelos/preset_ecualizador.dart';

/// Estado de reproducción expuesto al UI.
enum EstadoReproduccion { detenido, cargando, reproduciendo, pausado, error }

// ─────────────────────────────────────────────────────────────────────────────
// Handler global — inicializado en main.dart con AudioService.init
// ─────────────────────────────────────────────────────────────────────────────
PluriWaveAudioHandler? _handlerGlobal;

void registrarHandler(PluriWaveAudioHandler handler) {
  _handlerGlobal = handler;
}

/// Wrapper de alto nivel para el UI.
class ServicioAudio {
  PluriWaveAudioHandler get _handler {
    assert(
      _handlerGlobal != null,
      'registrarHandler() no fue llamado en main.dart',
    );
    return _handlerGlobal!;
  }

  Emisora? get emisoraActual => _handler.emisoraActual;

  Stream<EstadoReproduccion> get estadoStream =>
      _handler.playbackState.map((s) {
        if (s.processingState == AudioProcessingState.error) {
          return EstadoReproduccion.error;
        }
        if (s.processingState == AudioProcessingState.loading ||
            s.processingState == AudioProcessingState.buffering) {
          return EstadoReproduccion.cargando;
        }
        if (s.playing) return EstadoReproduccion.reproduciendo;
        if (s.processingState == AudioProcessingState.idle) {
          return EstadoReproduccion.detenido;
        }
        return EstadoReproduccion.pausado;
      });

  Future<void> reproducir(Emisora emisora) async {
    final item = MediaItem(
      id: emisora.url,
      title: emisora.nombre,
      artist: emisora.pais ?? '',
      album: 'PluriWave',
      artUri:
          emisora.favicon != null && emisora.favicon!.isNotEmpty
              ? Uri.tryParse(emisora.favicon!)
              : null,
      extras: {'uuid': emisora.uuid},
    );
    await _handler.playMediaItem(item);
  }

  Future<void> pausar() => _handler.pause();
  Future<void> reanudar() => _handler.play();

  Future<void> togglePlay() async {
    if (_handler.playbackState.value.playing) {
      await pausar();
    } else {
      await reanudar();
    }
  }

  Future<void> detener() => _handler.stop();
  Future<void> setVolumen(double vol) => _handler.setVolumen(vol);
  double get volumen => _handler.volumen;
  bool get estaSonando => _handler.playbackState.value.playing;
  Stream<int?> get androidAudioSessionIdStream async* {
    yield _handler.androidAudioSessionId;
    yield* _handler.androidAudioSessionIdStream;
  }

  Future<void> dispose() async {}

  // ── Ecualizador ───────────────────────────────────────────────────────────
  AndroidEqualizer? get ecualizador => _handler.ecualizador;
  bool get ecualizadorDisponible => _handler.ecualizadorDisponible;
  PresetEcualizador get presetActual => _handler.presetActual;

  Future<void> aplicarPreset(PresetEcualizador preset) =>
      _handler.aplicarPreset(preset);
  Future<void> setEcualizadorActivo(bool activo) =>
      _handler.setEcualizadorActivo(activo);

  Future<void> setBanda(int index, double db) => _handler.setBanda(index, db);
}

// ─────────────────────────────────────────────────────────────────────────────
// AudioHandler
// ─────────────────────────────────────────────────────────────────────────────
class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler {
  static const _timeoutCambioFuente = Duration(seconds: 12);
  static const _timeoutCierrePlayer = Duration(seconds: 3);

  AndroidEqualizer _eq = AndroidEqualizer();
  late AudioPlayer _player = _crearPlayer();
  StreamSubscription<PlayerState>? _estadoPlayerSub;
  StreamSubscription<Duration>? _bufferedSub;
  StreamSubscription<PlaybackEvent>? _eventosSub;
  StreamSubscription<int?>? _androidAudioSessionIdSub;
  final _androidAudioSessionIdController = StreamController<int?>.broadcast();
  int? _androidAudioSessionId;
  Future<void> _colaCambioFuente = Future<void>.value();
  int _revisionFuente = 0;

  Emisora? emisoraActual;
  double _volumen = 1.0;
  double get volumen => _volumen;

  AndroidEqualizer? get ecualizador => _eq;
  bool _eqDisponible = false;
  bool get ecualizadorDisponible => _eqDisponible;
  bool _ecualizadorActivo = true;
  bool get ecualizadorActivo => _ecualizadorActivo;

  PresetEcualizador _presetActual = PresetEcualizador.flat;
  PresetEcualizador get presetActual => _presetActual;
  int? get androidAudioSessionId => _androidAudioSessionId;
  Stream<int?> get androidAudioSessionIdStream =>
      _androidAudioSessionIdController.stream;

  PluriWaveAudioHandler() {
    _conectarStreamsPlayer();
  }

  AudioPlayer _crearPlayer() {
    return AudioPlayer(
      audioPipeline: AudioPipeline(androidAudioEffects: [_eq]),
    );
  }

  void _conectarStreamsPlayer() {
    _estadoPlayerSub = _player.playerStateStream.listen((state) {
      final playing = state.playing;
      final proc = state.processingState;
      playbackState.add(
        playbackState.value.copyWith(
          controls: [
            if (playing) MediaControl.pause else MediaControl.play,
            MediaControl.stop,
          ],
          systemActions: const {MediaAction.seek, MediaAction.stop},
          androidCompactActionIndices: const [0],
          processingState: _mapProcState(proc),
          playing: playing,
          bufferedPosition: _player.bufferedPosition,
          speed: _player.speed,
        ),
      );
    });

    _bufferedSub = _player.bufferedPositionStream.listen((pos) {
      playbackState.add(playbackState.value.copyWith(bufferedPosition: pos));
    });

    _eventosSub = _player.playbackEventStream.listen(
      (_) {},
      onError: (Object error, StackTrace stackTrace) {
        _gestionarErrorReproduccion(error);
      },
    );

    _androidAudioSessionIdSub = _player.androidAudioSessionIdStream.listen((
      sessionId,
    ) {
      _androidAudioSessionId = sessionId;
      if (!_androidAudioSessionIdController.isClosed) {
        _androidAudioSessionIdController.add(sessionId);
      }
    });
  }

  /// Gestiona cualquier error de reproducción de ExoPlayer.
  void _gestionarErrorReproduccion(Object error) {
    String mensaje;
    String codigoLog;

    if (error is PlayerException) {
      codigoLog = 'PlayerException(code=${error.code}): ${error.message}';
      mensaje = _mensajeAmigable(error);
    } else {
      codigoLog = 'Error desconocido: $error';
      mensaje = 'Error de reproducción';
    }

    developer.log(
      '[PluriWave] Error reproducción: $codigoLog',
      name: 'ServicioAudio',
      level: 900,
    );

    playbackState.add(
      playbackState.value.copyWith(
        processingState: AudioProcessingState.error,
        playing: false,
        errorMessage: mensaje,
      ),
    );
    emisoraActual = null;
    mediaItem.add(null);

    _player.stop().catchError((_) {});
  }

  /// Traduce códigos de error de ExoPlayer a mensajes para el usuario.
  String _mensajeAmigable(PlayerException e) {
    final code = e.code;

    if (code >= 2000 && code < 3000) {
      if (code == 2001) return 'Sin conexión a internet';
      if (code == 2002) return 'La URL de la radio no es válida';
      if (code == 2003) return 'La radio no está disponible (error 404)';
      if (code == 2004) return 'Tiempo de espera agotado al conectar';
      return 'No se puede conectar a la radio';
    }

    if (code >= 3000 && code < 4000) {
      return 'Formato de stream no compatible';
    }

    if (code >= 4000 && code < 5000) {
      return 'Error al decodificar el stream de audio';
    }

    final msg = e.message ?? '';
    if (msg.contains('Cleartext') || msg.contains('cleartext')) {
      return 'Esta radio usa HTTP sin cifrar (no permitido)';
    }
    if (msg.contains('CERTIFICATE') || msg.contains('HandshakeException')) {
      return 'Certificado SSL inválido en la radio';
    }

    return 'No se puede reproducir esta radio';
  }

  AudioProcessingState _mapProcState(ProcessingState state) {
    return switch (state) {
      ProcessingState.idle => AudioProcessingState.idle,
      ProcessingState.loading => AudioProcessingState.loading,
      ProcessingState.buffering => AudioProcessingState.buffering,
      ProcessingState.ready => AudioProcessingState.ready,
      ProcessingState.completed => AudioProcessingState.completed,
    };
  }

  @override
  Future<void> playMediaItem(MediaItem mediaItem) async {
    final revision = ++_revisionFuente;
    _colaCambioFuente = _colaCambioFuente
        .catchError((_) {})
        .then((_) => _cambiarFuente(mediaItem, revision));
    return _colaCambioFuente;
  }

  Future<void> _cambiarFuente(MediaItem mediaItem, int revision) async {
    this.mediaItem.add(mediaItem);
    emisoraActual = _emisoraDesdeMediaItem(mediaItem);
    playbackState.add(
      playbackState.value.copyWith(
        processingState: AudioProcessingState.loading,
        playing: false,
        errorMessage: null,
      ),
    );
    try {
      await _recrearPlayer();
      if (revision != _revisionFuente) return;

      await _player.setUrl(mediaItem.id).timeout(_timeoutCambioFuente);
      if (revision != _revisionFuente) return;

      _iniciarPlaySinBloquear(mediaItem, revision);
      unawaited(_activarEcualizador());
    } on PlayerException catch (e) {
      if (revision == _revisionFuente) {
        _gestionarErrorReproduccion(e);
      }
      throw Exception(_mensajeAmigable(e));
    } on Exception catch (e, stackTrace) {
      developer.log(
        '[PluriWave] Error inesperado en playMediaItem: $e',
        name: 'ServicioAudio',
        level: 900,
        stackTrace: stackTrace,
      );
      if (revision == _revisionFuente) {
        playbackState.add(
          playbackState.value.copyWith(
            processingState: AudioProcessingState.error,
            playing: false,
            errorMessage: 'Error inesperado al reproducir',
          ),
        );
        emisoraActual = null;
        this.mediaItem.add(null);
      }
      rethrow;
    }
  }

  Future<void> _recrearPlayer() async {
    await _estadoPlayerSub?.cancel();
    await _bufferedSub?.cancel();
    await _eventosSub?.cancel();
    await _androidAudioSessionIdSub?.cancel();

    final anterior = _player;
    try {
      await anterior.stop().timeout(_timeoutCierrePlayer);
    } catch (_) {}
    try {
      await anterior.dispose().timeout(_timeoutCierrePlayer);
    } catch (_) {}

    _eq = AndroidEqualizer();
    _eqDisponible = false;
    _androidAudioSessionId = null;
    _player = _crearPlayer();
    await _player.setVolume(_volumen);
    _conectarStreamsPlayer();
  }

  void _iniciarPlaySinBloquear(MediaItem mediaItem, int revision) {
    unawaited(
      _player.play().catchError((Object error, StackTrace stackTrace) {
        developer.log(
          '[PluriWave] Error al iniciar ${mediaItem.title}: $error',
          name: 'ServicioAudio',
          level: 900,
          stackTrace: stackTrace,
        );
        if (revision == _revisionFuente) {
          _gestionarErrorReproduccion(error);
        }
      }),
    );
  }

  Future<void> _activarEcualizador() async {
    try {
      final params = await _eq.parameters;
      _eqDisponible = params.bands.isNotEmpty;
      if (_eqDisponible) {
        await _eq.setEnabled(_ecualizadorActivo);
        await aplicarPreset(_presetActual);
      }
    } catch (_) {
      _eqDisponible = false;
    }
  }

  /// Aplica un preset al ecualizador nativo Android.
  Future<void> aplicarPreset(PresetEcualizador preset) async {
    _presetActual = preset;
    if (!_eqDisponible) return;
    try {
      await _eq.setEnabled(_ecualizadorActivo);
      if (!_ecualizadorActivo) return;
      final params = await _eq.parameters;
      for (
        int i = 0;
        i < params.bands.length && i < preset.bandas.length;
        i++
      ) {
        await params.bands[i].setGain(
          _mapearGananciaNativa(
            preset.bandas[i],
            minDecibels: params.minDecibels,
            maxDecibels: params.maxDecibels,
          ),
        );
      }
    } catch (_) {}
  }

  /// Ajusta una banda individual.
  Future<void> setBanda(int index, double db) async {
    final bandas = List<double>.from(_presetActual.bandas);
    if (index >= 0 && index < bandas.length) {
      bandas[index] = db;
      _presetActual = _presetActual.copyWithBandas(bandas);
    }
    if (!_eqDisponible || !_ecualizadorActivo) return;
    try {
      final params = await _eq.parameters;
      if (index < params.bands.length) {
        await params.bands[index].setGain(
          _mapearGananciaNativa(
            db,
            minDecibels: params.minDecibels,
            maxDecibels: params.maxDecibels,
          ),
        );
      }
    } catch (_) {}
  }

  double _mapearGananciaNativa(
    double db, {
    required double minDecibels,
    required double maxDecibels,
  }) {
    final normalizado = ((db.clamp(-12.0, 12.0) + 12.0) / 24.0).clamp(0.0, 1.0);
    return minDecibels + (normalizado * (maxDecibels - minDecibels));
  }

  Future<void> setEcualizadorActivo(bool activo) async {
    _ecualizadorActivo = activo;
    if (!_eqDisponible) return;
    try {
      await _eq.setEnabled(activo);
      if (activo) {
        await aplicarPreset(_presetActual);
      }
    } catch (_) {}
  }

  Future<void> setVolumen(double vol) async {
    _volumen = vol.clamp(0.0, 1.0);
    await _player.setVolume(_volumen);
  }

  @override
  Future<void> play() => _player.play();

  @override
  Future<void> pause() => _player.pause();

  @override
  Future<void> stop() async {
    _revisionFuente++;
    await _player.stop();
    emisoraActual = null;
    mediaItem.add(null);
    await super.stop();
  }

  @override
  Future<void> seek(Duration position) => _player.seek(position);

  @override
  Future<void> onTaskRemoved() async {
    await stop();
    await _estadoPlayerSub?.cancel();
    await _bufferedSub?.cancel();
    await _eventosSub?.cancel();
    await _androidAudioSessionIdSub?.cancel();
    await _player.dispose();
    await _androidAudioSessionIdController.close();
  }

  Emisora _emisoraDesdeMediaItem(MediaItem mediaItem) {
    final uuid = mediaItem.extras?['uuid'] as String? ?? mediaItem.id;
    return Emisora(
      uuid: uuid,
      nombre: mediaItem.title,
      url: mediaItem.id,
      pais: (mediaItem.artist?.isNotEmpty ?? false) ? mediaItem.artist : null,
      favicon: mediaItem.artUri?.toString(),
    );
  }
}
