import 'dart:math';
import 'package:flutter/foundation.dart';
import '../modelos/jugador.dart';
import '../modelos/partida.dart';
import '../modelos/palabra.dart';
import '../modelos/sala_multijugador.dart';
import '../servicios/servicio_notas.dart';

/// Estado global del juego gestionado con Provider
class EstadoJuego extends ChangeNotifier {
  BancoPalabras? _banco;
  Partida? _partida;
  final Map<String, String> _votos = {}; // votanteId -> votadoId
  bool _cargando = false;

  /// Jugador local del host en modo multi-dispositivo
  Jugador? _hostLocal;

  BancoPalabras? get banco => _banco;
  Partida? get partida => _partida;
  Map<String, String> get votos => Map.unmodifiable(_votos);
  bool get cargando => _cargando;

  /// Jugador local del host (para modo multi-dispositivo)
  Jugador? get hostLocal => _hostLocal;

  Future<void> cargarBanco() async {
    _cargando = true;
    notifyListeners();
    _banco = await BancoPalabras.cargar();
    _cargando = false;
    notifyListeners();
  }

  /// Establece el jugador local del host para modo multi-dispositivo
  void setHostJugador(String nombre) {
    _hostLocal = Jugador(
      id: 'host-local',
      nombre: nombre,
      endpointId: null, // El host local no tiene endpointId
    );
    notifyListeners();
  }

  /// Crea una nueva partida con la configuración dada y lista de jugadores
  void crearPartida({
    required ConfigPartida config,
    required List<String> nombresJugadores,
  }) {
    if (_banco == null) return;
    if (nombresJugadores.length < 3) return;

    // Seleccionar palabra
    final palabra = _banco!.palabraAleatoria(config.categoria);
    final categoriaReal =
        _banco!.categoriaDepalabra(palabra) ?? config.categoria;

    // Crear jugadores
    final jugadores = nombresJugadores.asMap().entries.map((e) {
      return Jugador(id: 'j${e.key}', nombre: e.value);
    }).toList();

    // Asignar impostores usando Random seguro (no predecible)
    final rng = Random.secure();
    final numImpostores = config.numImpostores.clamp(1, jugadores.length ~/ 3);
    final impostoresElegidos = <int>{};
    while (impostoresElegidos.length < numImpostores) {
      impostoresElegidos.add(rng.nextInt(jugadores.length));
    }
    for (final i in impostoresElegidos) {
      jugadores[i].esImpostor = true;
    }

    // Asignar palabras
    for (final j in jugadores) {
      if (!j.esImpostor) {
        j.palabra = palabra;
      }
    }

    _partida = Partida(
      config: config,
      jugadores: jugadores,
      palabraSecreta: palabra,
      categoriaReal: categoriaReal,
    );

    _votos.clear();
    ServicioNotas.limpiarNotas();
    notifyListeners();
  }

  /// Crea una partida multi-dispositivo usando los usuarios seleccionados de la
  /// sala como jugadores reales. La identidad de jugador se conserva por id y
  /// cada jugador queda asociado al endpoint del cliente que lo controla.
  void crearPartidaDesdeSala({
    required ConfigPartida config,
    required EstadoSalaMultijugador sala,
  }) {
    if (_banco == null) return;
    final usuariosSeleccionados = sala.usuariosSeleccionados;
    if (usuariosSeleccionados.length < 3) return;

    final palabra = _banco!.palabraAleatoria(config.categoria);
    final categoriaReal =
        _banco!.categoriaDepalabra(palabra) ?? config.categoria;

    final jugadores = usuariosSeleccionados.map((usuario) {
      final clienteId = usuario.clienteIdSeleccionado;
      final endpointId = clienteId == null
          ? null
          : sala.clientes[clienteId]?.endpointId;
      return Jugador(
        id: usuario.id,
        nombre: usuario.nombre,
        endpointId: endpointId,
      );
    }).toList();

    final rng = Random.secure();
    final numImpostores = config.numImpostores.clamp(1, jugadores.length ~/ 3);
    final impostoresElegidos = <int>{};
    while (impostoresElegidos.length < numImpostores) {
      impostoresElegidos.add(rng.nextInt(jugadores.length));
    }
    for (final i in impostoresElegidos) {
      jugadores[i].esImpostor = true;
    }

    for (final jugador in jugadores) {
      if (!jugador.esImpostor) {
        jugador.palabra = palabra;
      }
    }

    _partida = Partida(
      config: config,
      jugadores: jugadores,
      palabraSecreta: palabra,
      categoriaReal: categoriaReal,
    );

    _votos.clear();
    ServicioNotas.limpiarNotas();
    notifyListeners();
  }

  /// Avanza a la fase de debate
  void iniciarDebate() {
    if (_partida == null) return;
    _partida!.fase = FaseJuego.debate;
    notifyListeners();
  }

  /// Avanza a la fase de votación
  void iniciarVotacion() {
    if (_partida == null) return;
    _partida!.fase = FaseJuego.votacion;
    _votos.clear();
    notifyListeners();
  }

  /// Registra un voto (modo un solo móvil)
  void registrarVoto(String votanteId, String votadoId) {
    _votos[votanteId] = votadoId;
    notifyListeners();
  }

  /// Elimina un voto
  void eliminarVoto(String votanteId) {
    _votos.remove(votanteId);
    notifyListeners();
  }

  /// Comprueba si todos los jugadores activos (no eliminados) han votado
  bool todosHanVotado() {
    if (_partida == null) return false;
    final activos = _partida!.jugadoresActivos;
    return activos.every((j) => _votos.containsKey(j.id));
  }

  /// Procesa los votos y determina el eliminado
  ResultadoVotacion? procesarVotacion() {
    if (_partida == null) return null;

    // Contar votos
    final conteo = <String, int>{};
    for (final votado in _votos.values) {
      conteo[votado] = (conteo[votado] ?? 0) + 1;
    }

    if (conteo.isEmpty) return null;

    // Encontrar máximo
    final maxVotos = conteo.values.reduce(max);
    final masVotados = conteo.entries
        .where((e) => e.value == maxVotos)
        .toList();

    // En caso de empate, elegir aleatoriamente (usar Random.secure para consistencia)
    final rng = Random.secure();
    final eliminadoId = masVotados[rng.nextInt(masVotados.length)].key;

    final eliminado = _partida!.jugadores.firstWhere(
      (j) => j.id == eliminadoId,
    );
    eliminado.eliminado = true;

    final resultado = ResultadoVotacion(
      eliminadoId: eliminadoId,
      eliminadoNombre: eliminado.nombre,
      eraImpostor: eliminado.esImpostor,
      votos: Map.from(_votos),
    );

    _partida!.historialVotaciones.add(resultado);
    _partida!.fase = FaseJuego.resultado;
    notifyListeners();

    return resultado;
  }

  /// Comprueba si la partida ha terminado y actualiza el estado
  bool comprobarFinPartida() {
    if (_partida == null) return false;

    final impostoresVivos = _partida!.impostoresActivos.length;
    final jugadoresVivos = _partida!.jugadoresNormalesActivos.length;

    // Los jugadores ganan si no quedan impostores
    if (impostoresVivos == 0) {
      _partida!.ganador = 'jugadores';
      _partida!.fase = FaseJuego.finPartida;
      notifyListeners();
      return true;
    }

    // Los impostores ganan si son >= que los jugadores normales
    if (impostoresVivos >= jugadoresVivos) {
      _partida!.ganador = 'impostores';
      _partida!.fase = FaseJuego.finPartida;
      notifyListeners();
      return true;
    }

    return false;
  }

  /// Avanza a la fase de adivinanza del impostor
  void iniciarAdivinanza() {
    if (_partida == null) return;
    _partida!.fase = FaseJuego.adivinanza;
    notifyListeners();
  }

  /// El impostor intenta adivinar la palabra
  bool intentarAdivinar(String intento) {
    if (_partida == null) return false;
    final acierto =
        intento.trim().toLowerCase() ==
        _partida!.palabraSecreta.trim().toLowerCase();
    if (acierto) {
      _partida!.ganador = 'impostores';
      _partida!.fase = FaseJuego.finPartida;
      notifyListeners();
    }
    return acierto;
  }

  /// Inicia la siguiente ronda
  void siguienteRonda() {
    if (_partida == null) return;
    _partida!.rondaActual++;
    _partida!.fase = FaseJuego.debate;
    _votos.clear();
    notifyListeners();
  }

  /// Revancha: mismos jugadores, nueva palabra
  void revancha() {
    if (_partida == null || _banco == null) return;
    final nombres = _partida!.jugadores.map((j) => j.nombre).toList();
    crearPartida(config: _partida!.config, nombresJugadores: nombres);
  }

  /// Limpia la partida actual
  void limpiar() {
    _partida = null;
    _votos.clear();
    notifyListeners();
  }
}
