/* eslint-disable @typescript-eslint/member-ordering */
import { Injectable, inject } from '@angular/core';
import {
  Actions,
  createEffect,
  ofType
} from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { of, EMPTY } from 'rxjs';
import {
  mergeMap,
  withLatestFrom,
  tap,
  catchError
} from 'rxjs/operators';
import { RoomsActions } from './rooms.actions';
import { selectCurrentUser } from '../users/users.selectors';
import { selectCurrentRoom, selectSavedRooms } from './rooms.selectors';
import { RealtimeSessionFacade } from '../../core/realtime';
import { DatabaseService } from '../../infrastructure/persistence';
import { ServerDirectoryFacade } from '../../domains/server-directory';
import {
  normalizeRoomAccessControl,
  resolveRoomPermission,
  withLegacyRoomPermissions
} from '../../domains/access-control';
import { Room, RoomSettings } from '../../shared-kernel';
import {
  resolveRoom,
  getUserRoleForRoom,
  canManageChannelsInRoom
} from './rooms.helpers';
import { defaultChannels } from './room-channels.defaults';

/**
 * NgRx effects for room settings, permissions, channels, and icon updates.
 */
@Injectable()
export class RoomSettingsEffects {
  private actions$ = inject(Actions);
  private store = inject(Store);
  private webrtc = inject(RealtimeSessionFacade);
  private db = inject(DatabaseService);
  private serverDirectory = inject(ServerDirectoryFacade);

  /** Updates room settings (host/admin-only) and broadcasts changes to all peers. */
  updateRoomSettings$ = createEffect(() =>
    this.actions$.pipe(
      ofType(RoomsActions.updateRoomSettings),
      withLatestFrom(
        this.store.select(selectCurrentUser),
        this.store.select(selectCurrentRoom),
        this.store.select(selectSavedRooms)
      ),
      mergeMap(([
        { roomId, settings },
        currentUser,
        currentRoom,
        savedRooms
      ]) => {
        if (!currentUser)
          return of(RoomsActions.updateRoomSettingsFailure({ error: 'Not logged in' }));

        const room = resolveRoom(roomId, currentRoom, savedRooms);

        if (!room)
          return of(RoomsActions.updateRoomSettingsFailure({ error: 'Room not found' }));

        const currentUserRole = getUserRoleForRoom(room, currentUser, currentRoom);
        const isOwner = currentUserRole === 'host';
        const canManageRoom = isOwner || resolveRoomPermission(room, currentUser, 'manageServer');

        if (!canManageRoom) {
          return of(
            RoomsActions.updateRoomSettingsFailure({
              error: 'Permission denied'
            })
          );
        }

        const hasPasswordUpdate = Object.prototype.hasOwnProperty.call(settings, 'password');
        const normalizedPassword = typeof settings.password === 'string' ? settings.password.trim() : undefined;
        const nextHasPassword = typeof settings.hasPassword === 'boolean'
          ? settings.hasPassword
          : (hasPasswordUpdate
            ? !!normalizedPassword
            : (typeof room.hasPassword === 'boolean' ? room.hasPassword : !!room.password));
        const updatedSettings: RoomSettings = {
          name: settings.name ?? room.name,
          description: settings.description ?? room.description,
          topic: settings.topic ?? room.topic,
          isPrivate: settings.isPrivate ?? room.isPrivate,
          password: hasPasswordUpdate ? (normalizedPassword || '') : room.password,
          hasPassword: nextHasPassword,
          maxUsers: settings.maxUsers ?? room.maxUsers
        };
        const localRoomUpdates: Partial<Room> = {
          ...updatedSettings,
          password: hasPasswordUpdate ? (normalizedPassword || undefined) : room.password,
          hasPassword: nextHasPassword
        };
        const sharedSettings: RoomSettings = {
          name: updatedSettings.name,
          description: updatedSettings.description,
          topic: updatedSettings.topic,
          isPrivate: updatedSettings.isPrivate,
          hasPassword: nextHasPassword,
          maxUsers: updatedSettings.maxUsers,
          password: hasPasswordUpdate ? (normalizedPassword || '') : undefined
        };

        this.db.updateRoom(room.id, localRoomUpdates);

        this.webrtc.broadcastMessage({
          type: 'room-settings-update',
          roomId: room.id,
          settings: sharedSettings
        });

        if (canManageRoom) {
          this.serverDirectory.updateServer(room.id, {
            actingRole: currentUserRole ?? undefined,
            name: updatedSettings.name,
            description: updatedSettings.description,
            isPrivate: updatedSettings.isPrivate,
            maxUsers: updatedSettings.maxUsers,
            password: hasPasswordUpdate ? (normalizedPassword || null) : undefined
          }, {
            sourceId: room.sourceId,
            sourceUrl: room.sourceUrl
          }).subscribe({
            error: () => {}
          });
        }

        return of(RoomsActions.updateRoomSettingsSuccess({ roomId: room.id,
          settings: updatedSettings }));
      }),
      catchError((error) => of(RoomsActions.updateRoomSettingsFailure({ error: error.message })))
    )
  );

  /** Persists and broadcasts channel add/remove/rename changes. */
  syncChannelChanges$ = createEffect(
    () =>
      this.actions$.pipe(
        ofType(RoomsActions.addChannel, RoomsActions.removeChannel, RoomsActions.renameChannel),
        withLatestFrom(
          this.store.select(selectCurrentUser),
          this.store.select(selectCurrentRoom)
        ),
        tap(([
          , currentUser,
          currentRoom
        ]) => {
          if (!currentUser || !currentRoom) {
            return;
          }

          const role = getUserRoleForRoom(currentRoom, currentUser, currentRoom);

          if (!canManageChannelsInRoom(currentRoom, currentUser, currentRoom, role)) {
            return;
          }

          const channels = currentRoom.channels ?? defaultChannels();

          this.db.updateRoom(currentRoom.id, { channels });

          this.webrtc.broadcastMessage({
            type: 'channels-update',
            roomId: currentRoom.id,
            channels
          });

          this.serverDirectory.updateServer(currentRoom.id, {
            actingRole: role ?? undefined,
            channels
          }, {
            sourceId: currentRoom.sourceId,
            sourceUrl: currentRoom.sourceUrl
          }).subscribe({
            error: () => {}
          });
        })
      ),
    { dispatch: false }
  );

  /** Updates room permission grants (host-only) and broadcasts to peers. */
  updateRoomPermissions$ = createEffect(() =>
    this.actions$.pipe(
      ofType(RoomsActions.updateRoomPermissions),
      withLatestFrom(
        this.store.select(selectCurrentUser),
        this.store.select(selectCurrentRoom),
        this.store.select(selectSavedRooms)
      ),
      mergeMap(([
        { roomId, permissions },
        currentUser,
        currentRoom,
        savedRooms
      ]) => {
        if (!currentUser)
          return EMPTY;

        const room = resolveRoom(roomId, currentRoom, savedRooms);

        if (!room)
          return EMPTY;

        const nextRoom = withLegacyRoomPermissions(room, permissions);

        return of(RoomsActions.updateRoomAccessControl({
          roomId: room.id,
          changes: {
            roles: nextRoom.roles,
            roleAssignments: nextRoom.roleAssignments,
            channelPermissions: nextRoom.channelPermissions,
            slowModeInterval: nextRoom.slowModeInterval
          }
        }));
      })
    )
  );

  /** Updates role-based access control and broadcasts to peers and directory. */
  updateRoomAccessControl$ = createEffect(() =>
    this.actions$.pipe(
      ofType(RoomsActions.updateRoomAccessControl),
      withLatestFrom(
        this.store.select(selectCurrentUser),
        this.store.select(selectCurrentRoom),
        this.store.select(selectSavedRooms)
      ),
      mergeMap(([
        { roomId, changes },
        currentUser,
        currentRoom,
        savedRooms
      ]) => {
        if (!currentUser)
          return EMPTY;

        const room = resolveRoom(roomId, currentRoom, savedRooms);

        if (!room)
          return EMPTY;

        const requiresRoleManagement = !!changes.roles || !!changes.roleAssignments || !!changes.channelPermissions;
        const requiresServerManagement = typeof changes.slowModeInterval === 'number';
        const isOwner = room.hostId === currentUser.id || room.hostId === currentUser.oderId;
        const canManageRoles = isOwner || resolveRoomPermission(room, currentUser, 'manageRoles');
        const canManageServer = isOwner || resolveRoomPermission(room, currentUser, 'manageServer');

        if ((requiresRoleManagement && !canManageRoles) || (requiresServerManagement && !canManageServer)) {
          return EMPTY;
        }

        const nextRoom = normalizeRoomAccessControl({
          ...room,
          ...changes
        });
        const nextChanges: Partial<Room> = {
          roles: nextRoom.roles,
          roleAssignments: nextRoom.roleAssignments,
          channelPermissions: nextRoom.channelPermissions,
          slowModeInterval: nextRoom.slowModeInterval,
          permissions: nextRoom.permissions,
          members: nextRoom.members
        };

        this.webrtc.broadcastMessage({
          type: 'room-permissions-update',
          roomId: room.id,
          permissions: nextRoom.permissions,
          room: {
            roles: nextRoom.roles,
            roleAssignments: nextRoom.roleAssignments,
            channelPermissions: nextRoom.channelPermissions,
            slowModeInterval: nextRoom.slowModeInterval
          }
        });

        this.serverDirectory.updateServer(room.id, {
          roles: nextRoom.roles,
          roleAssignments: nextRoom.roleAssignments,
          channelPermissions: nextRoom.channelPermissions,
          slowModeInterval: nextRoom.slowModeInterval
        }, {
          sourceId: room.sourceId,
          sourceUrl: room.sourceUrl
        }).subscribe({
          error: () => {}
        });

        return of(RoomsActions.updateRoom({ roomId: room.id,
          changes: nextChanges }));
      })
    )
  );

  /** Updates the server icon (permission-enforced) and broadcasts to peers. */
  updateServerIcon$ = createEffect(() =>
    this.actions$.pipe(
      ofType(RoomsActions.updateServerIcon),
      withLatestFrom(
        this.store.select(selectCurrentUser),
        this.store.select(selectCurrentRoom),
        this.store.select(selectSavedRooms)
      ),
      mergeMap(([
        { roomId, icon },
        currentUser,
        currentRoom,
        savedRooms
      ]) => {
        if (!currentUser) {
          return of(RoomsActions.updateServerIconFailure({ error: 'Not logged in' }));
        }

        const room = resolveRoom(roomId, currentRoom, savedRooms);

        if (!room) {
          return of(RoomsActions.updateServerIconFailure({ error: 'Room not found' }));
        }

        const isOwner = room.hostId === currentUser.id || room.hostId === currentUser.oderId;
        const canByRole = resolveRoomPermission(room, currentUser, 'manageIcon');

        if (!isOwner && !canByRole) {
          return of(RoomsActions.updateServerIconFailure({ error: 'Permission denied' }));
        }

        const iconUpdatedAt = Date.now();
        const changes: Partial<Room> = { icon,
          iconUpdatedAt };

        this.db.updateRoom(room.id, changes);
        this.webrtc.broadcastMessage({
          type: 'server-icon-update',
          roomId: room.id,
          icon,
          iconUpdatedAt
        });

        this.webrtc.sendRawMessage({
          type: 'server_icon_available',
          serverId: room.id,
          iconUpdatedAt
        });

        this.serverDirectory.updateServer(room.id, {
          actingRole: isOwner ? 'host' : undefined,
          icon,
          iconUpdatedAt
        }, {
          sourceId: room.sourceId,
          sourceUrl: room.sourceUrl
        }).subscribe({
          error: () => {}
        });

        return of(RoomsActions.updateServerIconSuccess({ roomId: room.id,
          icon,
          iconUpdatedAt }));
      })
    )
  );
}
