import {
  Component,
  OnInit,
  inject,
  signal
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router';
import { firstValueFrom } from 'rxjs';
import { Store } from '@ngrx/store';
import { RoomsActions } from '../../../../store/rooms/rooms.actions';
import { UsersActions } from '../../../../store/users/users.actions';
import { selectCurrentUser } from '../../../../store/users/users.selectors';
import type { ServerInviteInfo } from '../../domain/models/server-directory.model';
import { STORAGE_KEY_CURRENT_USER_ID } from '../../../../core/constants';
import { DatabaseService } from '../../../../infrastructure/persistence';
import { ServerDirectoryFacade } from '../../application/facades/server-directory.facade';
import { User } from '../../../../shared-kernel';

@Component({
  selector: 'app-invite',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './invite.component.html'
})
export class InviteComponent implements OnInit {
  readonly currentUser = inject(Store).selectSignal(selectCurrentUser);
  readonly invite = signal<ServerInviteInfo | null>(null);
  readonly status = signal<'loading' | 'redirecting' | 'joining' | 'error'>('loading');
  readonly message = signal('Loading invite...');

  private readonly route = inject(ActivatedRoute);
  private readonly router = inject(Router);
  private readonly store = inject(Store);
  private readonly serverDirectory = inject(ServerDirectoryFacade);
  private readonly databaseService = inject(DatabaseService);

  async ngOnInit(): Promise<void> {
    const inviteContext = this.resolveInviteContext();

    if (!inviteContext) {
      return;
    }

    const currentUserId = localStorage.getItem(STORAGE_KEY_CURRENT_USER_ID);

    if (!currentUserId) {
      await this.redirectToLogin();
      return;
    }

    try {
      await this.joinInvite(inviteContext, currentUserId);
    } catch (error: unknown) {
      this.applyInviteError(error);
    }
  }

  goToSearch(): void {
    this.router.navigate(['/search']).catch(() => {});
  }

  private buildEndpointName(sourceUrl: string): string {
    try {
      const url = new URL(sourceUrl);

      return url.hostname;
    } catch {
      return 'Signal Server';
    }
  }

  private applyInviteError(error: unknown): void {
    const inviteError = error as {
      error?: { error?: string; errorCode?: string };
    };
    const errorCode = inviteError?.error?.errorCode;
    const fallbackMessage = inviteError?.error?.error || 'Unable to accept this invite.';

    this.status.set('error');

    if (errorCode === 'BANNED') {
      this.message.set('You are banned from this server and cannot accept this invite.');

      return;
    }

    if (errorCode === 'INVITE_EXPIRED') {
      this.message.set('This invite has expired. Ask for a fresh invite link.');

      return;
    }

    this.message.set(fallbackMessage);
  }

  private async hydrateCurrentUser(): Promise<User | null> {
    const currentUser = this.currentUser();

    if (currentUser) {
      return currentUser;
    }

    const storedUser = await this.databaseService.getCurrentUser();

    if (storedUser) {
      this.store.dispatch(UsersActions.setCurrentUser({ user: storedUser }));
    }

    return storedUser;
  }

  private async joinInvite(
    context: { endpoint: { id: string; name: string }; inviteId: string; sourceUrl: string },
    currentUserId: string
  ): Promise<void> {
    const invite = await firstValueFrom(this.serverDirectory.getInvite(context.inviteId, {
      sourceId: context.endpoint.id,
      sourceUrl: context.sourceUrl
    }));

    this.invite.set(invite);
    this.status.set('joining');
    this.message.set(`Joining ${invite.server.name}...`);

    const currentUser = await this.hydrateCurrentUser();
    const joinResponse = await firstValueFrom(this.serverDirectory.requestJoin({
      roomId: invite.server.id,
      userId: currentUserId,
      userPublicKey: currentUser?.oderId || currentUserId,
      displayName: currentUser?.displayName || 'Anonymous',
      inviteId: context.inviteId
    }, {
      sourceId: context.endpoint.id,
      sourceUrl: context.sourceUrl
    }));
    const resolvedSource = this.serverDirectory.normaliseRoomSignalSource({
      sourceId: joinResponse.server.sourceId ?? context.endpoint.id,
      sourceName: joinResponse.server.sourceName ?? context.endpoint.name,
      sourceUrl: joinResponse.server.sourceUrl ?? context.sourceUrl,
      signalingUrl: joinResponse.signalingUrl,
      fallbackName: joinResponse.server.sourceName ?? context.endpoint.name ?? invite.server.name
    }, {
      ensureEndpoint: true
    });

    this.store.dispatch(
      RoomsActions.joinRoom({
        roomId: joinResponse.server.id,
        serverInfo: {
          ...invite.server,
          ...joinResponse.server,
          channels:
            Array.isArray(joinResponse.server.channels) && joinResponse.server.channels.length > 0
              ? joinResponse.server.channels
              : invite.server.channels,
          ...resolvedSource,
          signalingUrl: joinResponse.signalingUrl
        }
      })
    );
  }

  private async redirectToLogin(): Promise<void> {
    this.status.set('redirecting');
    this.message.set('Redirecting to login...');

    await this.router.navigate(['/login'], {
      queryParams: {
        returnUrl: this.router.url
      }
    });
  }

  private resolveInviteContext(): {
    endpoint: { id: string; name: string };
    inviteId: string;
    sourceUrl: string;
  } | null {
    const inviteId = this.route.snapshot.paramMap.get('inviteId')?.trim() || '';
    const sourceUrl = this.route.snapshot.queryParamMap.get('server')?.trim() || '';

    if (!inviteId || !sourceUrl) {
      this.status.set('error');
      this.message.set('This invite link is missing required server information.');

      return null;
    }

    const endpoint = this.serverDirectory.ensureServerEndpoint({
      name: this.buildEndpointName(sourceUrl),
      url: sourceUrl
    }, {
      setActive: !localStorage.getItem(STORAGE_KEY_CURRENT_USER_ID)
    });

    return {
      endpoint: {
        id: endpoint.id,
        name: endpoint.name
      },
      inviteId,
      sourceUrl
    };
  }
}
