import 'usuario.dart';

enum FaseSalaMultijugador { lobby, enPartida, finalizada }

class ResultadoOperacionSala {
  final bool exitoso;
  final String? codigo;
  final String? mensaje;

  const ResultadoOperacionSala._({
    required this.exitoso,
    this.codigo,
    this.mensaje,
  });

  const ResultadoOperacionSala.ok([String? mensaje])
    : this._(exitoso: true, mensaje: mensaje);

  const ResultadoOperacionSala.error(String codigo, [String? mensaje])
    : this._(exitoso: false, codigo: codigo, mensaje: mensaje);

  Map<String, dynamic> toJson() => {
    'exitoso': exitoso,
    if (codigo != null) 'codigo': codigo,
    if (mensaje != null) 'mensaje': mensaje,
  };
}

class ClienteSala {
  final String clientId;
  final String? endpointId;
  final String nombre;
  final bool esHost;
  final bool conectado;
  final int ultimaActividadMs;

  const ClienteSala({
    required this.clientId,
    this.endpointId,
    required this.nombre,
    this.esHost = false,
    this.conectado = true,
    this.ultimaActividadMs = 0,
  });

  ClienteSala copiar({
    String? clientId,
    String? endpointId,
    String? nombre,
    bool? esHost,
    bool? conectado,
    int? ultimaActividadMs,
  }) {
    return ClienteSala(
      clientId: clientId ?? this.clientId,
      endpointId: endpointId ?? this.endpointId,
      nombre: nombre ?? this.nombre,
      esHost: esHost ?? this.esHost,
      conectado: conectado ?? this.conectado,
      ultimaActividadMs: ultimaActividadMs ?? this.ultimaActividadMs,
    );
  }

  Map<String, dynamic> toJson() => {
    'clientId': clientId,
    if (endpointId != null) 'endpointId': endpointId,
    'nombre': nombre,
    'esHost': esHost,
    'conectado': conectado,
    'ultimaActividadMs': ultimaActividadMs,
  };

  factory ClienteSala.fromJson(Map<String, dynamic> json) => ClienteSala(
    clientId: json['clientId'] as String,
    endpointId: json['endpointId'] as String?,
    nombre: json['nombre'] as String,
    esHost: json['esHost'] as bool? ?? false,
    conectado: json['conectado'] as bool? ?? true,
    ultimaActividadMs: (json['ultimaActividadMs'] as num?)?.toInt() ?? 0,
  );
}

class EstadoSalaMultijugador {
  final String roomId;
  final String nombreSala;
  final String hostClientId;
  FaseSalaMultijugador fase;
  final Map<String, ClienteSala> clientes;
  final Map<String, Usuario> usuarios;

  EstadoSalaMultijugador({
    required this.roomId,
    required this.nombreSala,
    required this.hostClientId,
    this.fase = FaseSalaMultijugador.lobby,
    Map<String, ClienteSala>? clientes,
    Map<String, Usuario>? usuarios,
  }) : clientes = clientes ?? {},
       usuarios = usuarios ?? {};

  factory EstadoSalaMultijugador.crear({
    required String roomId,
    required String nombreSala,
    required String hostClientId,
    required String hostNombre,
  }) {
    final sala = EstadoSalaMultijugador(
      roomId: roomId,
      nombreSala: nombreSala,
      hostClientId: hostClientId,
    );
    sala.registrarCliente(
      ClienteSala(
        clientId: hostClientId,
        nombre: hostNombre,
        esHost: true,
      ),
    );
    return sala;
  }

  List<Usuario> get usuariosSeleccionados =>
      usuarios.values.where((usuario) => usuario.estaSeleccionado).toList();

  List<Usuario> get usuariosDisponibles =>
      usuarios.values.where((usuario) => usuario.estaDisponible).toList();

  int get cantidadUsuariosSeleccionados => usuariosSeleccionados.length;

  List<ClienteSala> get clientesDesconectados => clientes.values
      .where((cliente) => !cliente.esHost && !cliente.conectado)
      .toList();

  List<Usuario> get usuariosDeClientesDesconectados {
    final desconectados = clientesDesconectados
        .map((cliente) => cliente.clientId)
        .toSet();
    return usuarios.values
        .where((usuario) => desconectados.contains(usuario.clienteIdSeleccionado))
        .toList();
  }

  List<Usuario> usuariosPorCliente(String clientId) {
    return usuarios.values
        .where((usuario) => usuario.clienteIdSeleccionado == clientId)
        .toList();
  }

  ClienteSala? clientePorEndpoint(String endpointId) {
    for (final cliente in clientes.values) {
      if (cliente.endpointId == endpointId) return cliente;
    }
    return null;
  }

  ResultadoOperacionSala registrarCliente(ClienteSala cliente) {
    clientes[cliente.clientId] = cliente;
    return const ResultadoOperacionSala.ok();
  }

  ResultadoOperacionSala crearUsuario(Usuario usuario) {
    if (fase != FaseSalaMultijugador.lobby) {
      return const ResultadoOperacionSala.error('sala_cerrada');
    }
    if (usuarios.containsKey(usuario.id)) {
      return const ResultadoOperacionSala.error('usuario_duplicado');
    }
    final nombreNormalizado = usuario.nombre.trim().toLowerCase();
    final nombreExiste = usuarios.values.any(
      (u) => u.nombre.trim().toLowerCase() == nombreNormalizado,
    );
    if (nombreExiste) {
      return const ResultadoOperacionSala.error('nombre_duplicado');
    }
    usuarios[usuario.id] = usuario;
    return const ResultadoOperacionSala.ok();
  }

  ResultadoOperacionSala seleccionarUsuario({
    required String usuarioId,
    required String clienteId,
  }) {
    if (fase != FaseSalaMultijugador.lobby) {
      return const ResultadoOperacionSala.error('sala_cerrada');
    }
    final cliente = clientes[clienteId];
    if (cliente == null || !cliente.conectado) {
      return const ResultadoOperacionSala.error('cliente_no_disponible');
    }
    final usuario = usuarios[usuarioId];
    if (usuario == null) {
      return const ResultadoOperacionSala.error('usuario_no_existe');
    }
    if (usuario.clienteIdSeleccionado == clienteId) {
      return const ResultadoOperacionSala.ok();
    }
    if (usuario.clienteIdSeleccionado != null) {
      return const ResultadoOperacionSala.error('usuario_ya_seleccionado');
    }
    usuarios[usuarioId] = usuario.copiar(clienteIdSeleccionado: clienteId);
    return const ResultadoOperacionSala.ok();
  }

  ResultadoOperacionSala liberarUsuario({
    required String usuarioId,
    required String solicitanteClientId,
  }) {
    if (fase != FaseSalaMultijugador.lobby) {
      return const ResultadoOperacionSala.error('sala_cerrada');
    }
    final usuario = usuarios[usuarioId];
    if (usuario == null) {
      return const ResultadoOperacionSala.error('usuario_no_existe');
    }
    final solicitante = clientes[solicitanteClientId];
    final puedeLiberar =
        usuario.clienteIdSeleccionado == solicitanteClientId ||
        (solicitante?.esHost ?? false);
    if (!puedeLiberar) {
      return const ResultadoOperacionSala.error('usuario_de_otro_cliente');
    }
    usuarios[usuarioId] = usuario.copiar(liberarSeleccion: true);
    return const ResultadoOperacionSala.ok();
  }

  ResultadoOperacionSala eliminarUsuario({
    required String usuarioId,
    required String solicitanteClientId,
  }) {
    final solicitante = clientes[solicitanteClientId];
    if (!(solicitante?.esHost ?? false)) {
      return const ResultadoOperacionSala.error('solo_host');
    }
    final usuario = usuarios[usuarioId];
    if (usuario == null) {
      return const ResultadoOperacionSala.error('usuario_no_existe');
    }
    if (usuario.estaSeleccionado) {
      return const ResultadoOperacionSala.error('usuario_seleccionado');
    }
    usuarios.remove(usuarioId);
    return const ResultadoOperacionSala.ok();
  }

  void desconectarCliente(String clientId) {
    final cliente = clientes[clientId];
    if (cliente == null) return;
    clientes[clientId] = cliente.copiar(conectado: false);
    if (fase != FaseSalaMultijugador.lobby) return;
    for (final entry in usuarios.entries.toList()) {
      if (entry.value.clienteIdSeleccionado == clientId) {
        usuarios[entry.key] = entry.value.copiar(liberarSeleccion: true);
      }
    }
  }

  void registrarActividadCliente(String clientId, {int? ahoraMs}) {
    final cliente = clientes[clientId];
    if (cliente == null) return;
    clientes[clientId] = cliente.copiar(
      conectado: true,
      ultimaActividadMs:
          ahoraMs ?? DateTime.now().millisecondsSinceEpoch,
    );
  }

  int reasignarUsuariosDeCliente({
    required String clientIdOrigen,
    required String clientIdDestino,
  }) {
    if (!clientes.containsKey(clientIdDestino)) return 0;
    var reasignados = 0;
    for (final entry in usuarios.entries.toList()) {
      if (entry.value.clienteIdSeleccionado == clientIdOrigen) {
        usuarios[entry.key] = entry.value.copiar(
          clienteIdSeleccionado: clientIdDestino,
        );
        reasignados++;
      }
    }
    return reasignados;
  }

  ResultadoOperacionSala validarInicio() {
    if (fase != FaseSalaMultijugador.lobby) {
      return const ResultadoOperacionSala.error('sala_cerrada');
    }
    if (cantidadUsuariosSeleccionados < 3) {
      return const ResultadoOperacionSala.error('faltan_jugadores');
    }
    if (usuariosPorCliente(hostClientId).isEmpty) {
      return const ResultadoOperacionSala.error('host_sin_usuario');
    }
    return const ResultadoOperacionSala.ok();
  }

  ResultadoOperacionSala iniciarPartida() {
    final validacion = validarInicio();
    if (!validacion.exitoso) return validacion;
    fase = FaseSalaMultijugador.enPartida;
    return const ResultadoOperacionSala.ok();
  }

  Map<String, dynamic> toJson() => {
    'roomId': roomId,
    'nombreSala': nombreSala,
    'hostClientId': hostClientId,
    'fase': fase.name,
    'clientes': clientes.values.map((cliente) => cliente.toJson()).toList(),
    'usuarios': usuarios.values.map((usuario) => usuario.toJson()).toList(),
  };

  factory EstadoSalaMultijugador.fromJson(Map<String, dynamic> json) {
    final clientes = <String, ClienteSala>{};
    for (final clienteJson in json['clientes'] as List<dynamic>? ?? []) {
      final cliente = ClienteSala.fromJson(
        clienteJson as Map<String, dynamic>,
      );
      clientes[cliente.clientId] = cliente;
    }

    final usuarios = <String, Usuario>{};
    for (final usuarioJson in json['usuarios'] as List<dynamic>? ?? []) {
      final usuario = Usuario.fromJson(usuarioJson as Map<String, dynamic>);
      usuarios[usuario.id] = usuario;
    }

    return EstadoSalaMultijugador(
      roomId: json['roomId'] as String,
      nombreSala: json['nombreSala'] as String,
      hostClientId: json['hostClientId'] as String,
      fase: FaseSalaMultijugador.values.firstWhere(
        (fase) => fase.name == json['fase'],
        orElse: () => FaseSalaMultijugador.lobby,
      ),
      clientes: clientes,
      usuarios: usuarios,
    );
  }
}
