import 'dart:convert';

import 'package:http/http.dart' as http;

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

/// Cliente para la Radio Browser API (https://api.radio-browser.info/).
///
/// Aplica reintentos acotados con rotación de host para tolerar fallos
/// transitorios al iniciar.
class ServicioRadio {
  static const _timeoutPorDefecto = Duration(seconds: 10);
  static const _maxIntentosPorDefecto = 3;
  static const _retryDelayPorDefecto = Duration(milliseconds: 250);

  // Servidores conocidos como fallback si el DNS falla.
  static const _servidoresFallback = [
    'de1.api.radio-browser.info',
    'nl1.api.radio-browser.info',
    'at1.api.radio-browser.info',
  ];

  ServicioRadio({
    http.Client? cliente,
    List<String>? servidores,
    int maxIntentos = _maxIntentosPorDefecto,
    Duration retryDelay = _retryDelayPorDefecto,
    Duration timeout = _timeoutPorDefecto,
  }) : _cliente = cliente ?? http.Client(),
       _servidores =
           (servidores == null || servidores.isEmpty)
               ? List<String>.from(_servidoresFallback)
               : List<String>.from(servidores),
       _maxIntentos = maxIntentos < 1 ? 1 : maxIntentos,
       _retryDelay = retryDelay,
       _timeout = timeout;

  final http.Client _cliente;
  final List<String> _servidores;
  final int _maxIntentos;
  final Duration _retryDelay;
  final Duration _timeout;

  String? _servidorActual;

  int _indiceServidorInicial() {
    if (_servidorActual == null) {
      return 0;
    }
    final index = _servidores.indexOf(_servidorActual!);
    return index >= 0 ? index : 0;
  }

  String _servidorPorIntento(int indiceBase, int intento) {
    final index = (indiceBase + intento) % _servidores.length;
    return _servidores[index];
  }

  Uri _uri(String servidor, String path, Map<String, String> params) {
    return Uri.https(servidor, path, {'hidebroken': 'true', ...params});
  }

  Future<List<Emisora>> _get(String path, Map<String, String> params) async {
    Exception? ultimoError;
    final indiceBase = _indiceServidorInicial();
    final totalIntentos = _maxIntentos;

    for (int intento = 0; intento < totalIntentos; intento++) {
      final servidor = _servidorPorIntento(indiceBase, intento);
      final uri = _uri(servidor, path, {'lastcheckok': '1', ...params});

      try {
        final resp = await _cliente
            .get(
              uri,
              headers: {
                'User-Agent': 'PluriWave/0.1.0 (es.freetimelab.pluriwave)',
              },
            )
            .timeout(_timeout);

        if (resp.statusCode != 200) {
          throw Exception('API error ${resp.statusCode}');
        }

        final lista = json.decode(resp.body) as List<dynamic>;
        _servidorActual = servidor;
        final emisoras =
            lista
                .cast<Map<String, dynamic>>()
                .map(Emisora.fromApi)
                .where((e) => e.uuid.isNotEmpty && e.url.isNotEmpty)
                .toList();
        emisoras.sort(_compararCalidad);
        return emisoras;
      } on Exception catch (e) {
        ultimoError = e;
        _servidorActual = null;

        final ultimoIntento = intento == (totalIntentos - 1);
        if (!ultimoIntento && _retryDelay > Duration.zero) {
          await Future<void>.delayed(_retryDelay);
        }
      }
    }

    throw ultimoError ?? Exception('Error desconocido al consultar la API');
  }

  /// Emisoras más votadas globalmente.
  Future<List<Emisora>> obtenerPopulares({
    int limit = 30,
    int offset = 0,
  }) async {
    return _get('/json/stations/search', {
      'limit': limit.toString(),
      'offset': offset.toString(),
      'order': 'bitrate',
      'reverse': 'true',
    });
  }

  /// Emisoras más escuchadas (por clicks) globalmente.
  Future<List<Emisora>> obtenerTendencias({int limit = 20}) async {
    final emisoras = await _get('/json/stations/topclick/$limit', {});
    emisoras.sort(_compararCalidad);
    return emisoras;
  }

  /// Buscar por nombre de emisora.
  Future<List<Emisora>> buscarPorNombre(
    String query, {
    int limit = 30,
    int offset = 0,
  }) async {
    return _get('/json/stations/search', {
      'name': query,
      'limit': limit.toString(),
      'offset': offset.toString(),
      'order': 'bitrate',
      'reverse': 'true',
    });
  }

  /// Buscar por código de país (ISO 3166-1 alpha-2, e.g. 'ES', 'US').
  Future<List<Emisora>> buscarPorPais(
    String codigoPais, {
    int limit = 50,
    int offset = 0,
  }) async {
    return _get('/json/stations/bycountrycodeexact/$codigoPais', {
      'limit': limit.toString(),
      'offset': offset.toString(),
      'order': 'bitrate',
      'reverse': 'true',
    });
  }

  /// Buscar por idioma (e.g. 'spanish', 'english').
  Future<List<Emisora>> buscarPorIdioma(
    String idioma, {
    int limit = 30,
    int offset = 0,
  }) async {
    return _get('/json/stations/bylanguageexact/$idioma', {
      'limit': limit.toString(),
      'offset': offset.toString(),
      'order': 'bitrate',
      'reverse': 'true',
    });
  }

  /// Buscar por tag/género (e.g. 'rock', 'jazz', 'pop').
  Future<List<Emisora>> buscarPorTag(
    String tag, {
    int limit = 30,
    int offset = 0,
  }) async {
    return _get('/json/stations/bytagexact/$tag', {
      'limit': limit.toString(),
      'offset': offset.toString(),
      'order': 'bitrate',
      'reverse': 'true',
    });
  }

  /// Búsqueda combinada: permite combinar nombre, país, idioma y tag.
  Future<List<Emisora>> buscar({
    String? nombre,
    String? pais,
    String? idioma,
    String? tag,
    int limit = 30,
    int offset = 0,
  }) async {
    return _get('/json/stations/search', {
      if (nombre != null && nombre.isNotEmpty) 'name': nombre,
      if (pais != null && pais.isNotEmpty) 'countrycode': pais,
      if (idioma != null && idioma.isNotEmpty) 'language': idioma,
      if (tag != null && tag.isNotEmpty) 'tag': tag,
      'limit': limit.toString(),
      'offset': offset.toString(),
      'order': 'bitrate',
      'reverse': 'true',
    });
  }

  int _compararCalidad(Emisora a, Emisora b) {
    final bitrateA = a.bitrate ?? 0;
    final bitrateB = b.bitrate ?? 0;
    final porBitrate = bitrateB.compareTo(bitrateA);
    if (porBitrate != 0) return porBitrate;

    final porClicks = b.clickcount.compareTo(a.clickcount);
    if (porClicks != 0) return porClicks;

    return b.votes.compareTo(a.votes);
  }

  /// Registrar un click en la API (best effort).
  Future<void> registrarClick(String uuid) async {
    try {
      final servidor =
          _servidorActual ?? _servidorPorIntento(_indiceServidorInicial(), 0);
      await _cliente
          .get(
            Uri.https(servidor, '/json/url/$uuid'),
            headers: {
              'User-Agent': 'PluriWave/0.1.0 (es.freetimelab.pluriwave)',
            },
          )
          .timeout(_timeout);
    } catch (_) {
      // No crítico, ignorar.
    }
  }
}
