import { v4 as uuidv4 } from 'uuid';
import {
  Room,
  BanEntry,
  User
} from '../../shared-kernel';
import { resolveLegacyRole, resolveRoomPermission } from '../../domains/access-control';
import { findRoomMember } from './room-members.helpers';
import { ROOM_URL_PATTERN } from '../../core/constants';

/** Build a minimal User object from signaling payload. */
export function buildSignalingUser(data: { oderId: string; displayName?: string; status?: string }, extras: Record<string, unknown> = {}) {
  const displayName = data.displayName?.trim() || 'User';
  const rawStatus = ([
    'online',
    'away',
    'busy',
    'offline'
  ] as const).includes(data.status as 'online')
    ? (data.status as 'online' | 'away' | 'busy' | 'offline')
    : 'online';
  // 'offline' from the server means the user chose Invisible;
  // display them as disconnected to other users.
  const status = rawStatus === 'offline' ? ('disconnected' as const) : rawStatus;

  return {
    oderId: data.oderId,
    id: data.oderId,
    username: displayName.toLowerCase().replace(/\s+/g, '_'),
    displayName,
    status,
    isOnline: true,
    role: 'member' as const,
    joinedAt: Date.now(),
    ...extras
  };
}

/** Best-known persisted member metadata for a signaling user in the viewed room. */
export function buildKnownUserExtras(room: Room | null, identifier: string): Record<string, unknown> {
  const knownMember = room ? findRoomMember(room.members ?? [], identifier) : undefined;

  if (!knownMember)
    return {};

  return {
    username: knownMember.username,
    description: knownMember.description,
    profileUpdatedAt: knownMember.profileUpdatedAt,
    avatarUrl: knownMember.avatarUrl,
    avatarHash: knownMember.avatarHash,
    avatarMime: knownMember.avatarMime,
    avatarUpdatedAt: knownMember.avatarUpdatedAt,
    role: knownMember.role,
    joinedAt: knownMember.joinedAt
  };
}

/** Returns true when the message's server ID does not match the viewed server. */
export function isWrongServer(msgServerId: string | undefined, viewedServerId: string | undefined): boolean {
  return !!(msgServerId && viewedServerId && msgServerId !== viewedServerId);
}

export function resolveUserDisplayName(user: Pick<User, 'displayName' | 'username'> | null | undefined): string {
  const displayName = user?.displayName?.trim();

  if (displayName) {
    return displayName;
  }

  return user?.username?.trim() || 'User';
}

export function hasPersistedChannels(channels: Room['channels'] | undefined): channels is NonNullable<Room['channels']> {
  return Array.isArray(channels) && channels.length > 0;
}

/** Keep cached channels until directory metadata provides a concrete replacement. */
export function resolveRoomChannels(
  cachedChannels: Room['channels'] | undefined,
  incomingChannels: Room['channels'] | undefined
): Room['channels'] | undefined {
  if (hasPersistedChannels(incomingChannels)) {
    return incomingChannels;
  }

  if (hasPersistedChannels(cachedChannels)) {
    return cachedChannels;
  }

  return undefined;
}

/**
 * Peer room-state snapshots can lag behind cached room metadata.
 * Keep richer cached channels until an equally rich or richer snapshot arrives.
 */
export function reconcileRoomSnapshotChannels(
  cachedChannels: Room['channels'] | undefined,
  incomingChannels: Room['channels'] | undefined
): Room['channels'] | undefined {
  if (hasPersistedChannels(cachedChannels) && !hasPersistedChannels(incomingChannels)) {
    return cachedChannels;
  }

  if (hasPersistedChannels(cachedChannels) && hasPersistedChannels(incomingChannels)) {
    return incomingChannels.length >= cachedChannels.length ? incomingChannels : cachedChannels;
  }

  if (hasPersistedChannels(incomingChannels)) {
    return incomingChannels;
  }

  return undefined;
}

export function resolveTextChannelId(channels: Room['channels'] | undefined, preferredChannelId?: string | null): string | null {
  const textChannels = (channels ?? []).filter((channel) => channel.type === 'text');

  if (preferredChannelId && textChannels.some((channel) => channel.id === preferredChannelId)) {
    return preferredChannelId;
  }

  return textChannels[0]?.id ?? null;
}

export function resolveRoom(roomId: string | undefined, currentRoom: Room | null, savedRooms: Room[]): Room | null {
  if (!roomId)
    return currentRoom;

  if (currentRoom?.id === roomId)
    return currentRoom;

  return savedRooms.find((room) => room.id === roomId) ?? null;
}

export function sanitizeRoomSnapshot(room: Partial<Room>): Partial<Room> {
  return {
    name: typeof room.name === 'string' ? room.name : undefined,
    description: typeof room.description === 'string' ? room.description : undefined,
    topic: typeof room.topic === 'string' ? room.topic : undefined,
    hostId: typeof room.hostId === 'string' ? room.hostId : undefined,
    hasPassword:
      typeof room.hasPassword === 'boolean' ? room.hasPassword : typeof room.password === 'string' ? room.password.trim().length > 0 : undefined,
    isPrivate: typeof room.isPrivate === 'boolean' ? room.isPrivate : undefined,
    maxUsers: typeof room.maxUsers === 'number' ? room.maxUsers : undefined,
    icon: typeof room.icon === 'string' ? room.icon : undefined,
    iconUpdatedAt: typeof room.iconUpdatedAt === 'number' ? room.iconUpdatedAt : undefined,
    slowModeInterval: typeof room.slowModeInterval === 'number' ? room.slowModeInterval : undefined,
    permissions: room.permissions ? { ...room.permissions } : undefined,
    channels: hasPersistedChannels(room.channels) ? room.channels : undefined,
    members: Array.isArray(room.members) ? room.members : undefined,
    roles: Array.isArray(room.roles) ? room.roles : undefined,
    roleAssignments: Array.isArray(room.roleAssignments) ? room.roleAssignments : undefined,
    channelPermissions: Array.isArray(room.channelPermissions) ? room.channelPermissions : undefined,
    sourceId: typeof room.sourceId === 'string' ? room.sourceId : undefined,
    sourceName: typeof room.sourceName === 'string' ? room.sourceName : undefined,
    sourceUrl: typeof room.sourceUrl === 'string' ? room.sourceUrl : undefined
  };
}

export function normalizeIncomingBans(roomId: string, bans: unknown): BanEntry[] {
  if (!Array.isArray(bans))
    return [];

  const now = Date.now();

  return bans
    .filter((ban): ban is Partial<BanEntry> => !!ban && typeof ban === 'object')
    .map((ban) => ({
      oderId: typeof ban.oderId === 'string' ? ban.oderId : uuidv4(),
      userId: typeof ban.userId === 'string' ? ban.userId : '',
      roomId,
      bannedBy: typeof ban.bannedBy === 'string' ? ban.bannedBy : '',
      displayName: typeof ban.displayName === 'string' ? ban.displayName : undefined,
      reason: typeof ban.reason === 'string' ? ban.reason : undefined,
      expiresAt: typeof ban.expiresAt === 'number' ? ban.expiresAt : undefined,
      timestamp: typeof ban.timestamp === 'number' ? ban.timestamp : now
    }))
    .filter((ban) => !!ban.userId && !!ban.bannedBy && (!ban.expiresAt || ban.expiresAt > now));
}

export function getUserRoleForRoom(room: Room, currentUser: User, currentRoom: Room | null): User['role'] | null {
  return resolveLegacyRole(currentRoom?.id === room.id ? currentRoom : room, currentUser);
}

export function canManageChannelsInRoom(
  room: Room,
  currentUser: User,
  currentRoom: Room | null,
  currentUserRole = getUserRoleForRoom(room, currentUser, currentRoom)
): boolean {
  return currentUserRole === 'host' || resolveRoomPermission(room, currentUser, 'manageChannels');
}

export function getPersistedCurrentUserId(): string | null {
  return localStorage.getItem('metoyou_currentUserId');
}

export function extractRoomIdFromUrl(url: string): string | null {
  const roomMatch = url.match(ROOM_URL_PATTERN);

  return roomMatch ? roomMatch[1] : null;
}

export interface RoomPresenceSignalingMessage {
  type: string;
  reason?: string;
  serverId?: string;
  serverIds?: string[];
  users?: {
    oderId: string;
    displayName: string;
    description?: string;
    profileUpdatedAt?: number;
    homeSignalServerUrl?: string;
    status?: string;
  }[];
  oderId?: string;
  displayName?: string;
  description?: string;
  fromUserId?: string;
  icon?: string;
  iconUpdatedAt?: number;
  profileUpdatedAt?: number;
  homeSignalServerUrl?: string;
  status?: string;
  voiceState?: {
    isConnected?: boolean;
    isMuted?: boolean;
    isDeafened?: boolean;
    isSpeaking?: boolean;
    roomId?: string;
    serverId?: string;
    clientInstanceId?: string;
  };
  isConnected?: boolean;
  isMuted?: boolean;
  isDeafened?: boolean;
  isSpeaking?: boolean;
  roomId?: string;
  clientInstanceId?: string;
}
