import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { GameStreamEvent, TournamentStreamEvent } from '../models/tournament.models';
import { TournamentAuthService } from './tournament-auth.service';

@Injectable({ providedIn: 'root' })
export class TournamentStreamService {
  private readonly auth = inject(TournamentAuthService);

  streamTournament(serverUrl: string, tournamentId: string): Observable<TournamentStreamEvent> {
    return this.ndjson<TournamentStreamEvent>(
      serverUrl,
      `/api/tournament/${tournamentId}/stream`
    );
  }

  streamGame(serverUrl: string, tournamentId: string, gameId: string): Observable<GameStreamEvent> {
    return this.ndjson<GameStreamEvent>(
      serverUrl,
      `/api/tournament/${tournamentId}/game/${gameId}/stream`
    );
  }

  private fullUrl(base: string, path: string): string {
    if (!base) return path;
    return `${base.replace(/\/+$/, '')}${path}`;
  }

  private ndjson<T>(serverUrl: string, path: string): Observable<T> {
    return new Observable<T>(subscriber => {
      const controller = new AbortController();

      (async () => {
        try {
          const res = await this.openWithRetry(serverUrl, path, controller.signal);
          if (!res.ok || !res.body) {
            subscriber.error(new Error(`Stream failed: ${res.status} ${res.statusText}`));
            return;
          }
          const reader = res.body.getReader();
          const decoder = new TextDecoder();
          let buffer = '';
          while (true) {
            const { value, done } = await reader.read();
            if (done) break;
            buffer += decoder.decode(value, { stream: true });
            let nl: number;
            while ((nl = buffer.indexOf('\n')) >= 0) {
              const line = buffer.slice(0, nl).trim();
              buffer = buffer.slice(nl + 1);
              if (!line) continue;
              try {
                subscriber.next(JSON.parse(line) as T);
              } catch {
                // Drop malformed lines silently — server may emit partial keep-alives.
              }
            }
          }
          subscriber.complete();
        } catch (err) {
          if ((err as Error).name !== 'AbortError') subscriber.error(err);
        }
      })();

      return () => controller.abort();
    });
  }

  private async openWithRetry(serverUrl: string, path: string, signal: AbortSignal): Promise<Response> {
    const url = this.fullUrl(serverUrl, path);
    const token = await this.auth.getToken(serverUrl);
    const res = await fetch(url, {
      headers: { Accept: 'application/x-ndjson', Authorization: `Bearer ${token}` },
      signal,
    });
    if (res.status !== 401) return res;
    this.auth.clearToken(serverUrl);
    const fresh = await this.auth.getToken(serverUrl);
    return fetch(url, {
      headers: { Accept: 'application/x-ndjson', Authorization: `Bearer ${fresh}` },
      signal,
    });
  }
}
