import {
  Room,
  RoomMember,
  RoomRole,
  RoomRoleAssignment
} from '../../../../shared-kernel';
import { SYSTEM_ROLE_IDS } from '../constants/access-control.constants';
import { localizeSystemRoleDisplayName } from './role-display.rules';
import type { MemberIdentity } from '../models/access-control.model';
import {
  buildRoleLookup,
  compareText,
  memberIdentityKey,
  matchesIdentity,
  roleSortDescending,
  uniqueStrings
} from '../util/access-control.util';
import { getRoomRoleById, normalizeRoomRoles } from './role.rules';

function sortAssignments(assignments: readonly RoomRoleAssignment[]): RoomRoleAssignment[] {
  return [...assignments].sort((firstAssignment, secondAssignment) =>
    compareText(firstAssignment.oderId || firstAssignment.userId, secondAssignment.oderId || secondAssignment.userId)
  );
}

function resolveLegacyMemberRoleIds(member: RoomMember, validRoleIds: Set<string>): string[] {
  if (Array.isArray(member.roleIds) && member.roleIds.length > 0) {
    return uniqueStrings(member.roleIds).filter((roleId) => validRoleIds.has(roleId));
  }

  if (member.role === 'admin') {
    return [SYSTEM_ROLE_IDS.admin];
  }

  if (member.role === 'moderator') {
    return [SYSTEM_ROLE_IDS.moderator];
  }

  return [];
}

export function normalizeRoomRoleAssignments(
  assignments: readonly RoomRoleAssignment[] | undefined,
  members: readonly RoomMember[] | undefined,
  roles: readonly RoomRole[]
): RoomRoleAssignment[] {
  const validRoleIds = new Set(roles.map((role) => role.id).filter((roleId) => roleId !== SYSTEM_ROLE_IDS.everyone));
  const normalizedByUserKey = new Map<string, RoomRoleAssignment>();
  const hasExplicitAssignments = Array.isArray(assignments);

  for (const assignment of assignments ?? []) {
    if (!assignment || typeof assignment !== 'object') {
      continue;
    }

    const userId = typeof assignment.userId === 'string' ? assignment.userId.trim() : '';
    const oderId = typeof assignment.oderId === 'string' ? assignment.oderId.trim() : undefined;
    const key = oderId || userId;

    if (!key) {
      continue;
    }

    const roleIds = uniqueStrings(assignment.roleIds ?? []).filter((roleId) => validRoleIds.has(roleId));

    if (roleIds.length === 0) {
      continue;
    }

    normalizedByUserKey.set(key, {
      userId: userId || key,
      oderId,
      roleIds
    });
  }

  if (hasExplicitAssignments) {
    return sortAssignments(Array.from(normalizedByUserKey.values()));
  }

  for (const member of members ?? []) {
    const key = memberIdentityKey(member);

    if (!key) {
      continue;
    }

    const roleIds = resolveLegacyMemberRoleIds(member, validRoleIds);

    if (roleIds.length === 0) {
      continue;
    }

    normalizedByUserKey.set(key, {
      userId: member.id || key,
      oderId: member.oderId || undefined,
      roleIds
    });
  }

  return sortAssignments(Array.from(normalizedByUserKey.values()));
}

export function getAssignedRoleIds(assignments: readonly RoomRoleAssignment[] | undefined, identity: MemberIdentity | null | undefined): string[] {
  const assignment = (assignments ?? []).find((candidateAssignment) => matchesIdentity(identity, candidateAssignment));

  return uniqueStrings(assignment?.roleIds ?? []);
}

export function getDisplayRoleName(
  room: Room,
  member: MemberIdentity | null | undefined,
  translate?: (key: string) => string
): string {
  const translateOr = (key: string, fallback: string) => translate?.(key) ?? fallback;

  if (!member) {
    return translateOr('shared.leaveServer.roles.member', 'Member');
  }

  if (room.hostId === member.id || room.hostId === member.oderId) {
    return translateOr('shared.leaveServer.roles.owner', 'Owner');
  }

  const roles = normalizeRoomRoles(room.roles, room.permissions);
  const roleLookup = buildRoleLookup(roles);
  const assignedRoles = getAssignedRoleIds(room.roleAssignments, member)
    .map((roleId) => roleLookup.get(roleId))
    .filter((role): role is RoomRole => !!role)
    .sort(roleSortDescending);
  const roleName = assignedRoles[0]?.name;

  if (!roleName) {
    return translateOr('shared.accessControl.roles.everyone', '@everyone');
  }

  return translate ? localizeSystemRoleDisplayName(roleName, translate) : roleName;
}

export function getAssignedRoles(room: Room, identity: MemberIdentity | null | undefined): RoomRole[] {
  const roles = normalizeRoomRoles(room.roles, room.permissions);
  const roleLookup = buildRoleLookup(roles);

  return getAssignedRoleIds(room.roleAssignments, identity)
    .map((roleId) => roleLookup.get(roleId))
    .filter((role): role is RoomRole => !!role)
    .sort(roleSortDescending);
}

export function getHighestAssignedRole(room: Room, identity: MemberIdentity | null | undefined): RoomRole | null {
  if (!identity) {
    return null;
  }

  if (room.hostId === identity.id || room.hostId === identity.oderId) {
    return null;
  }

  return getAssignedRoles(room, identity)[0] ?? getRoomRoleById(normalizeRoomRoles(room.roles, room.permissions), SYSTEM_ROLE_IDS.everyone) ?? null;
}

export function setRoleAssignmentsForMember(
  assignments: readonly RoomRoleAssignment[] | undefined,
  member: MemberIdentity,
  roleIds: readonly string[]
): RoomRoleAssignment[] {
  const nextAssignments = new Map<string, RoomRoleAssignment>();
  const memberKey = memberIdentityKey(member);

  for (const assignment of assignments ?? []) {
    const key = memberIdentityKey({
      id: assignment.userId,
      oderId: assignment.oderId
    });

    if (!key || key === memberKey) {
      continue;
    }

    nextAssignments.set(key, {
      userId: assignment.userId,
      oderId: assignment.oderId,
      roleIds: uniqueStrings(assignment.roleIds)
    });
  }

  const normalizedRoleIds = uniqueStrings(roleIds);

  if (memberKey && normalizedRoleIds.length > 0) {
    nextAssignments.set(memberKey, {
      userId: member.id || member.oderId || memberKey,
      oderId: member.oderId || undefined,
      roleIds: normalizedRoleIds
    });
  }

  return sortAssignments(Array.from(nextAssignments.values()));
}

export function removeRoleFromAssignments(assignments: readonly RoomRoleAssignment[] | undefined, roleId: string): RoomRoleAssignment[] {
  return (assignments ?? [])
    .map((assignment) => ({
      ...assignment,
      roleIds: assignment.roleIds.filter((candidateRoleId) => candidateRoleId !== roleId)
    }))
    .filter((assignment) => assignment.roleIds.length > 0);
}

export function getRoleIdsForMember(room: Room, member: MemberIdentity | null | undefined): string[] {
  return getAssignedRoleIds(
    normalizeRoomRoleAssignments(room.roleAssignments, room.members, normalizeRoomRoles(room.roles, room.permissions)),
    member
  );
}
