import {
  Injectable,
  inject,
  computed,
  type Signal
} from '@angular/core';
import { Store } from '@ngrx/store';
import { selectCurrentUser, selectOnlineUsers } from '../../../../store/users/users.selectors';
import { toSignal } from '@angular/core/rxjs-interop';
import { RealtimeSessionFacade } from '../../../../core/realtime';
import type { User } from '../../../../shared-kernel';

/**
 * Connectivity health status for a single peer in voice.
 */
export interface PeerConnectivityHealth {
  peerId: string;
  /** Number of voice peers this peer can send/receive audio to/from. */
  connectedPeerCount: number;
  /** Total peers expected in voice. */
  totalVoicePeers: number;
  /** true when this peer has the fewest connections -> warning target. */
  hasDesync: boolean;
}

/**
 * Tracks per-peer voice connectivity health by comparing the number
 * of connected audio streams each peer has. Peers with fewest
 * bidirectional audio connections are flagged.
 *
 * Uses peer latency data as proxy for healthy bidirectional connection.
 */
@Injectable({ providedIn: 'root' })
export class VoiceConnectivityHealthService {
  readonly currentUser: Signal<User | null | undefined>;
  readonly onlineUsers: Signal<User[]>;
  readonly desyncPeerIds: Signal<ReadonlySet<string>>;
  readonly localUserHasDesync: Signal<boolean>;

  private readonly webrtc = inject(RealtimeSessionFacade);

  constructor() {
    const store = inject(Store);

    this.currentUser = toSignal(store.select(selectCurrentUser));
    this.onlineUsers = toSignal(store.select(selectOnlineUsers), { initialValue: [] });

    /**
     * Map of peerId -> true for peers that have connectivity issues.
     * A peer is flagged when it has fewer healthy connections than the
     * majority of users in the same voice channel.
     */
    this.desyncPeerIds = computed<ReadonlySet<string>>(() => {
      const me = this.currentUser();
      const myVoice = me?.voiceState;

      if (!myVoice?.isConnected || !myVoice.roomId || !myVoice.serverId) {
        return new Set<string>();
      }

      // Find all users in same voice room
      const voiceUsers = this.onlineUsers().filter(
        (user) =>
          user.voiceState?.isConnected
          && user.voiceState.roomId === myVoice.roomId
          && user.voiceState.serverId === myVoice.serverId
      );

      if (voiceUsers.length < 2) {
        return new Set<string>();
      }

      // Use peer latencies as proxy. A peer we can ping has a working
      // data-channel (= working RTCPeerConnection). Peers without latency
      // measurements are considered unreachable.
      const connectedPeers = this.webrtc.connectedPeers();
      const connectedSet = new Set(connectedPeers);
      const myKey = me?.oderId || me?.id;

      if (!myKey) {
        return new Set<string>();
      }

      // Count how many voice peers each voice user is connected to (from
      // the local perspective). We can only see our own connections - but
      // if WE can't reach peer X while we CAN reach peers Y and Z, peer X
      // is the one with issues.
      const unreachableFromUs = new Set<string>();

      for (const user of voiceUsers) {
        const key = user.oderId || user.id;

        if (key === myKey) {
          continue;
        }

        const hasConnection = connectedSet.has(key)
          || connectedSet.has(user.id)
          || connectedSet.has(user.oderId ?? '');

        if (!hasConnection) {
          unreachableFromUs.add(key);
        }
      }

      // If we can reach everyone, no desync
      if (unreachableFromUs.size === 0) {
        return new Set<string>();
      }

      // If we can't reach ANYONE, the problem is likely on our end
      const reachableCount = voiceUsers.length - 1 - unreachableFromUs.size;

      if (reachableCount === 0 && voiceUsers.length > 2) {
        // Everyone unreachable from us -> WE are the problem
        return new Set([myKey]);
      }

      return unreachableFromUs;
    });

    /**
     * Whether the LOCAL user is the one with connectivity issues.
     */
    this.localUserHasDesync = computed(() => {
      const me = this.currentUser();
      const myKey = me?.oderId || me?.id;

      return !!myKey && this.desyncPeerIds().has(myKey);
    });
  }

  /**
   * Check if a specific peer has a desync warning.
   */
  hasPeerDesync(peerKey: string): boolean {
    return this.desyncPeerIds().has(peerKey);
  }
}
