import { Injectable, inject } from '@angular/core';
import { Router } from '@angular/router';
import { environment } from '../../environments/environment';
import { ChallengeEventService } from './challenge-event.service';
import { ChallengeService } from './challenge.service';

@Injectable({ providedIn: 'root' })
export class ChallengeWebSocketService {
  private readonly challengeEventService = inject(ChallengeEventService);
  private readonly challengeService = inject(ChallengeService);
  private readonly router = inject(Router);

  private ws: WebSocket | null = null;
  private reconnectAttempts = 0;
  private readonly maxReconnectAttempts = 5;
  private readonly reconnectDelay = 3000;
  private intentionalClose = false;

  connect(): void {
    if (this.ws) return;

    const token = localStorage.getItem('token');
    if (!token) return;

    const url = `${environment.userWsBaseUrl}/api/user/ws`;

    try {
      this.intentionalClose = false;
      this.ws = new WebSocket(url);

      this.ws.onopen = () => {
        this.reconnectAttempts = 0;
        this.ws?.send(JSON.stringify({ type: 'auth', token }));
      };

      this.ws.onmessage = (event) => {
        this.handleMessage(event.data as string);
      };

      this.ws.onerror = () => {
        // onclose fires right after, handles reconnect
      };

      this.ws.onclose = () => {
        this.ws = null;
        if (!this.intentionalClose) {
          this.attemptReconnect();
        }
      };
    } catch {
      this.attemptReconnect();
    }
  }

  disconnect(): void {
    this.intentionalClose = true;
    this.reconnectAttempts = 0;
    if (this.ws) {
      this.ws.close();
      this.ws = null;
    }
  }

  private handleMessage(data: string): void {
    let message: Record<string, unknown>;
    try {
      message = JSON.parse(data) as Record<string, unknown>;
    } catch {
      return;
    }

    switch (message['type']) {
      case 'CONNECTED':
        break;

      case 'challengeCreated': {
        const challengeId = message['challengeId'] as string | undefined;
        if (challengeId) {
          this.challengeService.getChallenge(challengeId).subscribe({
            next: (challenge) => this.challengeEventService.onChallengeReceived(challenge),
            error: () => {
              /* challenge may have already expired */
            },
          });
        }
        break;
      }

      case 'challengeAccepted': {
        const challengeId = message['challengeId'] as string | undefined;
        const gameId = message['gameId'] as string | undefined;
        if (challengeId) {
          this.challengeEventService.removeChallenge(challengeId);
        }
        if (gameId) {
          void this.router.navigate(['/game', gameId]);
        }
        break;
      }

      case 'challengeDeclined':
      case 'challengeExpired':
      case 'challengeCancelled': {
        const challengeId = message['challengeId'] as string | undefined;
        if (challengeId) {
          this.challengeEventService.removeChallenge(challengeId);
        }
        break;
      }
    }
  }

  private attemptReconnect(): void {
    if (this.intentionalClose || this.reconnectAttempts >= this.maxReconnectAttempts) return;
    this.reconnectAttempts++;
    setTimeout(() => {
      this.connect();
    }, this.reconnectDelay);
  }
}
