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

import 'package:flutter_test/flutter_test.dart';
import 'package:pluriwave/estado/estado_radio.dart';
import 'package:pluriwave/modelos/emisora.dart';
import 'package:pluriwave/modelos/preset_ecualizador.dart';
import 'package:pluriwave/servicios/servicio_audio.dart';

import '../helpers/fakes.dart';

void main() {
  group('EstadoRadio integración de custom + EQ persistente', () {
    test('incluye emisoras custom en el listado principal de inicio', () async {
      final archivo = await _crearArchivoCustom([
        emisoraDemo(uuid: 'custom-1', nombre: 'Custom Uno'),
      ]);
      final estado = EstadoRadio(
        audio: FakeServicioAudio(),
        favoritos: FakeServicioFavoritos(),
        radio: FakeServicioRadio(
          populares: [emisoraDemo(uuid: 'api-1', nombre: 'API Uno')],
        ),
        servicioEcualizador: FakeServicioEcualizador(),
        resolverArchivoCustom: () async => archivo,
        iniciarAutomaticamente: false,
      );

      await estado.inicializar();

      expect(
        estado.emisorasInicio.map((e) => e.uuid).toList(),
        equals(['custom-1', 'api-1']),
      );
    });

    test(
      'carga EQ principal persistido antes de decidir EQ de reproducción',
      () async {
        final audio = FakeServicioAudio();
        final principal = PresetEcualizador.rock;
        final emisora = emisoraDemo(uuid: 'api-1', nombre: 'API Uno');
        final estado = EstadoRadio(
          audio: audio,
          favoritos: FakeServicioFavoritos(),
          radio: FakeServicioRadio(populares: [emisora]),
          servicioEcualizador: FakeServicioEcualizador(principal: principal),
          resolverArchivoCustom: _archivoCustomVacio,
          iniciarAutomaticamente: false,
        );

        await estado.inicializar();
        await estado.reproducir(emisora);

        expect(estado.presetEcualizador, principal);
        expect(audio.presetsAplicados.first, principal);
        expect(audio.presetsAplicados.last, principal);
      },
    );

    test(
      'mantiene EQ persistido aunque el ecualizador nativo no esté disponible',
      () async {
        final principal = PresetEcualizador.jazz;
        final porEmisora = {'fav-1': PresetEcualizador.rock};
        final estado = EstadoRadio(
          audio: FakeServicioAudio(ecualizadorActivo: false),
          favoritos: FakeServicioFavoritos(),
          radio: FakeServicioRadio(),
          servicioEcualizador: FakeServicioEcualizador(
            principal: principal,
            porEmisora: porEmisora,
          ),
          resolverArchivoCustom: _archivoCustomVacio,
          iniciarAutomaticamente: false,
        );

        await estado.inicializar();

        expect(estado.ecualizadorDisponible, isFalse);
        expect(estado.presetEcualizador, principal);
        expect(estado.presetPrincipalEcualizador, principal);
        expect(
          estado.presetEcualizadorPorEmisora('fav-1'),
          PresetEcualizador.rock,
        );
      },
    );

    test(
      'inicializar deja error tras fallo y cargarPopulares manual recupera estaciones',
      () async {
        final radio = FakeServicioRadio(
          erroresPopularesPorLlamada: [Exception('sin red')],
          popularesPorLlamada: [
            const [],
            [emisoraDemo(uuid: 'api-ok', nombre: 'API Recuperada')],
          ],
          tendenciasPorLlamada: [
            const [],
            [emisoraDemo(uuid: 'trend-ok', nombre: 'Trend Recuperada')],
          ],
        );
        final estado = EstadoRadio(
          audio: FakeServicioAudio(),
          favoritos: FakeServicioFavoritos(),
          radio: radio,
          servicioEcualizador: FakeServicioEcualizador(),
          resolverArchivoCustom: _archivoCustomVacio,
          iniciarAutomaticamente: false,
        );

        await estado.inicializar();
        expect(estado.error, 'Sin conexión a la API de radio');

        await estado.cargarPopulares();

        expect(estado.error, isNull);
        expect(estado.populares.map((e) => e.uuid), contains('api-ok'));
        expect(estado.tendencias.map((e) => e.uuid), contains('trend-ok'));
        expect(radio.obtenerPopularesCalls, 2);
      },
    );

    test(
      'EQ propio por emisora pisa al principal y puede volver a fallback',
      () async {
        final audio = FakeServicioAudio();
        final favoritos = FakeServicioFavoritos();
        final emisora = emisoraDemo(uuid: 'fav-1', nombre: 'Favorita');
        final principal = PresetEcualizador.pop;
        final propio = PresetEcualizador.jazz;
        await favoritos.agregar(emisora);

        final estado = EstadoRadio(
          audio: audio,
          favoritos: favoritos,
          radio: FakeServicioRadio(populares: [emisora]),
          servicioEcualizador: FakeServicioEcualizador(principal: principal),
          resolverArchivoCustom: _archivoCustomVacio,
          iniciarAutomaticamente: false,
        );

        await estado.inicializar();
        await estado.cargarFavoritos();
        await estado.guardarPresetEcualizadorPorEmisora(emisora.uuid, propio);

        await estado.reproducir(emisora);
        expect(estado.presetEcualizador, propio);
        expect(audio.presetsAplicados.last, propio);

        await estado.deshabilitarPresetEcualizadorPorEmisora(emisora.uuid);
        await estado.reproducir(emisora);
        expect(estado.presetEcualizador, principal);
        expect(audio.presetsAplicados.last, principal);
      },
    );

    test(
      'favorita sin EQ propio usa EQ principal desde el primer play',
      () async {
        final audio = FakeServicioAudio();
        final favoritos = FakeServicioFavoritos();
        final emisora = emisoraDemo(uuid: 'fav-main', nombre: 'Favorita Main');
        final principal = PresetEcualizador.voz;
        await favoritos.agregar(emisora);

        final estado = EstadoRadio(
          audio: audio,
          favoritos: favoritos,
          radio: FakeServicioRadio(populares: [emisora]),
          servicioEcualizador: FakeServicioEcualizador(principal: principal),
          resolverArchivoCustom: _archivoCustomVacio,
          iniciarAutomaticamente: false,
        );

        await estado.inicializar();
        await estado.cargarFavoritos();
        await estado.reproducir(emisora);

        expect(estado.tienePresetEcualizadorPorEmisora(emisora.uuid), isFalse);
        expect(estado.presetEcualizador, principal);
        expect(audio.presetsAplicados.last, principal);
      },
    );

    test(
      'permite activar y desactivar el ecualizador de forma persistente',
      () async {
        final audio = FakeServicioAudio();
        final servicioEcualizador = FakeServicioEcualizador();
        final estado = EstadoRadio(
          audio: audio,
          favoritos: FakeServicioFavoritos(),
          radio: FakeServicioRadio(),
          servicioEcualizador: servicioEcualizador,
          resolverArchivoCustom: _archivoCustomVacio,
          iniciarAutomaticamente: false,
        );

        await estado.inicializar();
        expect(estado.ecualizadorActivo, isTrue);

        await estado.cambiarEcualizadorActivo(false);
        expect(estado.ecualizadorActivo, isFalse);
        expect(servicioEcualizador.config.activo, isFalse);
        expect(audio.cambiosEcualizadorActivo.last, isFalse);

        await estado.cambiarEcualizadorActivo(true);
        expect(estado.ecualizadorActivo, isTrue);
        expect(servicioEcualizador.config.activo, isTrue);
        expect(audio.cambiosEcualizadorActivo.last, isTrue);
      },
    );

    test(
      'notifica cambios de estado de audio para mostrar reproductor al primer play',
      () async {
        final audio = FakeServicioAudio();
        final emisora = emisoraDemo(uuid: 'play-1', nombre: 'Primera');
        final estado = EstadoRadio(
          audio: audio,
          favoritos: FakeServicioFavoritos(),
          radio: FakeServicioRadio(populares: [emisora]),
          servicioEcualizador: FakeServicioEcualizador(),
          resolverArchivoCustom: _archivoCustomVacio,
          iniciarAutomaticamente: false,
        );
        var notificaciones = 0;
        estado.addListener(() => notificaciones++);

        await estado.inicializar();
        final antes = notificaciones;
        audio.emitirEstado(EstadoReproduccion.cargando);
        await Future<void>.delayed(Duration.zero);

        expect(notificaciones, greaterThan(antes));
      },
    );

    test(
      'reproducir la misma emisora mientras suena fuerza recarga del stream',
      () async {
        final audio = FakeServicioAudio();
        final emisora = emisoraDemo(uuid: 'same-1', nombre: 'Misma');
        final estado = EstadoRadio(
          audio: audio,
          favoritos: FakeServicioFavoritos(),
          radio: FakeServicioRadio(populares: [emisora]),
          servicioEcualizador: FakeServicioEcualizador(),
          resolverArchivoCustom: _archivoCustomVacio,
          iniciarAutomaticamente: false,
        );

        await estado.inicializar();
        await estado.reproducir(emisora);
        await estado.reproducir(emisora);

        expect(audio.emisorasReproducidas, hasLength(2));
      },
    );

    test(
      'ignora finalizaciones stale cuando se cambia de emisora rapido',
      () async {
        final audio = _AudioControlado();
        final radio = FakeServicioRadio();
        final primera = emisoraDemo(uuid: 'slow-1', nombre: 'Lenta');
        final segunda = emisoraDemo(uuid: 'fast-2', nombre: 'Rapida');
        final estado = EstadoRadio(
          audio: audio,
          favoritos: FakeServicioFavoritos(),
          radio: radio,
          servicioEcualizador: FakeServicioEcualizador(),
          resolverArchivoCustom: _archivoCustomVacio,
          iniciarAutomaticamente: false,
        );

        await estado.inicializar();
        await estado.guardarPresetEcualizadorPorEmisora(
          primera.uuid,
          PresetEcualizador.rock,
        );
        await estado.guardarPresetEcualizadorPorEmisora(
          segunda.uuid,
          PresetEcualizador.jazz,
        );

        final primeraFuture = estado.reproducir(primera);
        final segundaFuture = estado.reproducir(segunda);
        audio.completar(segunda.uuid);
        await segundaFuture;
        audio.completar(primera.uuid);
        await primeraFuture;

        expect(estado.presetEcualizador, PresetEcualizador.jazz);
        expect(radio.ultimoUuidClick, segunda.uuid);
      },
    );

    test('reordenar favoritos reindexa de forma determinística', () async {
      final favoritos = FakeServicioFavoritos();
      await favoritos.agregar(emisoraDemo(uuid: 'a', nombre: 'A'));
      await favoritos.agregar(emisoraDemo(uuid: 'b', nombre: 'B'));
      await favoritos.agregar(emisoraDemo(uuid: 'c', nombre: 'C'));

      await favoritos.reordenar('a', 2);
      final lista = await favoritos.obtenerTodos();

      expect(lista.map((e) => e.uuid).toList(), equals(['b', 'c', 'a']));
      expect(lista.map((e) => e.orden).toList(), equals([0, 1, 2]));
    });

    test('cargarMasBusqueda pagina resultados y acota memoria', () async {
      final emisoras = List.generate(
        70,
        (i) => emisoraDemo(uuid: 'page-$i', nombre: 'Page $i'),
      );
      final estado = EstadoRadio(
        audio: FakeServicioAudio(),
        favoritos: FakeServicioFavoritos(),
        radio: FakeServicioRadio(busqueda: emisoras),
        servicioEcualizador: FakeServicioEcualizador(),
        resolverArchivoCustom: _archivoCustomVacio,
        iniciarAutomaticamente: false,
      );

      await estado.inicializar();
      await estado.buscar(nombre: 'page');
      expect(estado.resultadosBusqueda, hasLength(30));
      expect(estado.hayMasBusqueda, isTrue);

      await estado.cargarMasBusqueda();
      expect(estado.resultadosBusqueda, hasLength(60));

      await estado.cargarMasBusqueda();
      expect(estado.resultadosBusqueda, hasLength(70));
      expect(estado.hayMasBusqueda, isFalse);
    });

    test('toggleFavorito refresca lista global y evita estado stale', () async {
      final favoritos = FakeServicioFavoritos();
      final emisora = emisoraDemo(uuid: 'fav-sync', nombre: 'Sync');
      final estado = EstadoRadio(
        audio: FakeServicioAudio(),
        favoritos: favoritos,
        radio: FakeServicioRadio(populares: [emisora]),
        servicioEcualizador: FakeServicioEcualizador(),
        resolverArchivoCustom: _archivoCustomVacio,
        iniciarAutomaticamente: false,
      );

      await estado.inicializar();
      expect(estado.listaFavoritos.any((e) => e.uuid == emisora.uuid), isFalse);

      await estado.toggleFavorito(emisora);
      expect(estado.listaFavoritos.any((e) => e.uuid == emisora.uuid), isTrue);

      await estado.toggleFavorito(emisora);
      expect(estado.listaFavoritos.any((e) => e.uuid == emisora.uuid), isFalse);
    });
  });
}

class _AudioControlado extends ServicioAudio {
  final _estadoController = StreamController<EstadoReproduccion>.broadcast();
  final _pendientes = <String, Completer<void>>{};
  Emisora? _actual;

  @override
  Emisora? get emisoraActual => _actual;

  @override
  Stream<EstadoReproduccion> get estadoStream => _estadoController.stream;

  @override
  Future<void> reproducir(Emisora emisora) async {
    final completer = Completer<void>();
    _pendientes[emisora.uuid] = completer;
    _actual = emisora;
    _estadoController.add(EstadoReproduccion.cargando);
    await completer.future;
    _estadoController.add(EstadoReproduccion.reproduciendo);
  }

  void completar(String uuid) {
    _pendientes.remove(uuid)?.complete();
  }

  @override
  Future<void> aplicarPreset(PresetEcualizador preset) async {}

  @override
  Future<void> dispose() async {
    await _estadoController.close();
  }
}

Future<File> _archivoCustomVacio() async => _crearArchivoCustom(const []);

Future<File> _crearArchivoCustom(List<dynamic> emisoras) async {
  final dir = await Directory.systemTemp.createTemp('pluriwave-test-');
  final archivo = File('${dir.path}/emisoras_custom.json');
  await archivo.writeAsString(
    jsonEncode(emisoras.map((e) => e.toMap()).toList()),
  );
  return archivo;
}
