import {
  ChannelPermissionOverride,
  PermissionState,
  Room,
  RoomPermissionKey,
  RoomRole
} from '../../../../shared-kernel';
import { SYSTEM_ROLE_IDS } from '../constants/access-control.constants';
import type { MemberIdentity } from '../models/access-control.model';
import {
  buildRoleLookup,
  getRolePermissionState,
  matchesIdentity,
  normalizePermissionState,
  roleSortAscending,
  compareText
} from '../util/access-control.util';
import { getAssignedRoleIds, getHighestAssignedRole } from './role-assignment.rules';
import { getRoomRoleById, normalizeRoomRoles } from './role.rules';

function resolveRolePermissionState(roles: readonly RoomRole[], assignedRoleIds: readonly string[], permission: RoomPermissionKey): PermissionState {
  const roleLookup = buildRoleLookup(roles);
  const assignedRoles = assignedRoleIds.map((roleId) => roleLookup.get(roleId)).filter((role): role is RoomRole => !!role);
  const effectiveRoles = [roleLookup.get(SYSTEM_ROLE_IDS.everyone), ...assignedRoles]
    .filter((role): role is RoomRole => !!role)
    .sort(roleSortAscending);

  let state: PermissionState = 'inherit';

  for (const role of effectiveRoles) {
    const nextState = getRolePermissionState(role, permission);

    if (nextState !== 'inherit') {
      state = nextState;
    }
  }

  return state;
}

function resolveChannelOverrideState(
  overrides: readonly ChannelPermissionOverride[],
  roles: readonly RoomRole[],
  assignedRoleIds: readonly string[],
  identity: MemberIdentity,
  channelId: string,
  permission: RoomPermissionKey,
  baseState: PermissionState
): PermissionState {
  const roleLookup = buildRoleLookup(roles);

  let state = baseState;

  const everyoneOverride = overrides.find(
    (override) =>
      override.channelId === channelId &&
      override.targetType === 'role' &&
      override.targetId === SYSTEM_ROLE_IDS.everyone &&
      override.permission === permission
  );

  if (everyoneOverride?.value && everyoneOverride.value !== 'inherit') {
    state = everyoneOverride.value;
  }

  const orderedAssignedRoles = assignedRoleIds
    .map((roleId) => roleLookup.get(roleId))
    .filter((role): role is RoomRole => !!role)
    .sort(roleSortAscending);

  for (const role of orderedAssignedRoles) {
    const override = overrides.find(
      (candidateOverride) =>
        candidateOverride.channelId === channelId &&
        candidateOverride.targetType === 'role' &&
        candidateOverride.targetId === role.id &&
        candidateOverride.permission === permission
    );

    if (override?.value && override.value !== 'inherit') {
      state = override.value;
    }
  }

  const userOverride = overrides.find(
    (override) =>
      override.channelId === channelId &&
      override.targetType === 'user' &&
      override.permission === permission &&
      (override.targetId === identity.id || override.targetId === identity.oderId)
  );

  if (userOverride?.value && userOverride.value !== 'inherit') {
    state = userOverride.value;
  }

  return state;
}

export function normalizeChannelPermissionOverrides(
  overrides: readonly ChannelPermissionOverride[] | undefined,
  roles: readonly RoomRole[]
): ChannelPermissionOverride[] {
  const validRoleIds = new Set(roles.map((role) => role.id));
  const normalizedByKey = new Map<string, ChannelPermissionOverride>();

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

    const channelId = typeof override.channelId === 'string' ? override.channelId.trim() : '';
    const targetId = typeof override.targetId === 'string' ? override.targetId.trim() : '';
    const targetType = override.targetType === 'role' || override.targetType === 'user' ? override.targetType : null;
    const permission = override.permission;
    const value = normalizePermissionState(override.value);

    if (!channelId || !targetId || !targetType || !permission || value === 'inherit') {
      continue;
    }

    if (targetType === 'role' && !validRoleIds.has(targetId)) {
      continue;
    }

    normalizedByKey.set(`${channelId}:${targetType}:${targetId}:${permission}`, {
      channelId,
      targetType,
      targetId,
      permission,
      value
    });
  }

  return Array.from(normalizedByKey.values()).sort((firstOverride, secondOverride) => {
    const channelCompare = compareText(firstOverride.channelId, secondOverride.channelId);

    if (channelCompare !== 0) {
      return channelCompare;
    }

    if (firstOverride.targetType !== secondOverride.targetType) {
      return compareText(firstOverride.targetType, secondOverride.targetType);
    }

    const targetCompare = compareText(firstOverride.targetId, secondOverride.targetId);

    if (targetCompare !== 0) {
      return targetCompare;
    }

    return compareText(firstOverride.permission, secondOverride.permission);
  });
}

export function resolveRoomPermission(
  room: Room,
  identity: MemberIdentity | null | undefined,
  permission: RoomPermissionKey,
  channelId?: string
): boolean {
  if (!identity) {
    return false;
  }

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

  const roles = normalizeRoomRoles(room.roles, room.permissions);
  const assignedRoleIds = getAssignedRoleIds(room.roleAssignments, identity);
  const roleState = resolveRolePermissionState(roles, assignedRoleIds, permission);
  const channelState = channelId
    ? resolveChannelOverrideState(
      normalizeChannelPermissionOverrides(room.channelPermissions, roles),
      roles,
      assignedRoleIds,
      identity,
      channelId,
      permission,
      roleState
    )
    : roleState;

  return channelState === 'allow';
}

export function canManageMember(
  room: Room,
  actor: MemberIdentity | null | undefined,
  target: MemberIdentity | null | undefined,
  permission: 'kickMembers' | 'banMembers' | 'manageRoles'
): boolean {
  if (!actor || !target) {
    return false;
  }

  const isActorOwner = room.hostId === actor.id || room.hostId === actor.oderId;
  const isTargetOwner = room.hostId === target.id || room.hostId === target.oderId;
  const isSameIdentity = matchesIdentity(actor, {
    userId: target.id || target.oderId || '',
    oderId: target.oderId
  });

  if (isSameIdentity) {
    return false;
  }

  if (isTargetOwner && !isActorOwner) {
    return false;
  }

  if (isActorOwner) {
    return true;
  }

  if (!resolveRoomPermission(room, actor, permission)) {
    return false;
  }

  const actorRole = getHighestAssignedRole(room, actor);
  const targetRole = getHighestAssignedRole(room, target);

  return (actorRole?.position ?? 0) > (targetRole?.position ?? 0);
}

export function canManageRole(room: Room, actor: MemberIdentity | null | undefined, roleId: string): boolean {
  if (!actor || !roleId) {
    return false;
  }

  if (room.hostId === actor.id || room.hostId === actor.oderId) {
    return true;
  }

  if (!resolveRoomPermission(room, actor, 'manageRoles')) {
    return false;
  }

  const targetRole = getRoomRoleById(normalizeRoomRoles(room.roles, room.permissions), roleId);
  const actorRole = getHighestAssignedRole(room, actor);

  if (!targetRole) {
    return false;
  }

  return (actorRole?.position ?? 0) > targetRole.position;
}
