import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import {
  ErrorEvent,
  GameFull,
  GameState,
  GameStreamEvent,
  LegalMovesResponse,
  PlayerInfo
} from '../models/game.models';

@Injectable({ providedIn: 'root' })
export class GameApiService {
  private readonly apiBase = environment.apiBaseUrl;
  private readonly wsBase = environment.wsBaseUrl;
  private readonly apiPath = environment.apiPath;

  constructor(private readonly http: HttpClient) {}

  createGame(): Observable<GameFull> {
    return this.http.post<GameFull>(`${this.apiBase}${this.apiPath}`, {});
  }

  createGameVsBot(difficulty: 'easy' | 'medium' | 'hard' = 'medium'): Observable<GameFull> {
    const playerColor = Math.random() > 0.5 ? 'white' : 'black';
    const playerInfo: PlayerInfo = {
      id: `player-${Date.now()}`,
      displayName: 'You'
    };
    const botInfo: PlayerInfo = {
      id: `bot-${difficulty}`,
      displayName: `Bot (${difficulty})`
    };

    const payload =
      playerColor === 'white'
        ? { white: playerInfo, black: botInfo }
        : { white: botInfo, black: playerInfo };

    return this.http.post<GameFull>(`${this.apiBase}${this.apiPath}`, payload);
  }

  getGame(gameId: string): Observable<GameFull> {
    return this.http.get<GameFull>(`${this.apiBase}${this.apiPath}/${gameId}`);
  }

  makeMove(gameId: string, uci: string): Observable<GameState> {
    return this.http.post<GameState>(`${this.apiBase}${this.apiPath}/${gameId}/move/${uci}`, {});
  }

  getLegalMoves(gameId: string, square?: string): Observable<LegalMovesResponse> {
    let params = new HttpParams();
    if (square) {
      params = params.set('square', square);
    }
    return this.http.get<LegalMovesResponse>(`${this.apiBase}${this.apiPath}/${gameId}/moves`, { params });
  }

  importFen(fen: string): Observable<GameFull> {
    return this.http.post<GameFull>(`${this.apiBase}${this.apiPath}/import/fen`, { fen });
  }

  importPgn(pgn: string): Observable<GameFull> {
    return this.http.post<GameFull>(`${this.apiBase}${this.apiPath}/import/pgn`, { pgn });
  }

  private resolveWsBase(): string {
    if (this.wsBase) {
      return this.wsBase;
    }

    const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
    return `${wsProtocol}://${window.location.host}`;
  }

  streamGame(gameId: string): Observable<GameStreamEvent> {
    return new Observable<GameStreamEvent>((observer) => {
      const wsUrl = `${this.resolveWsBase()}${this.apiPath}/${gameId}/stream`;
      const streamUrl = `${this.apiBase}${this.apiPath}/${gameId}/stream`;
      const ws = new WebSocket(wsUrl);
      const abortController = new AbortController();
      let connected = false;
      let fallbackActive = false;

      const parseEvent = (raw: string): GameStreamEvent | null => {
        if (!raw.trim()) {
          return null;
        }

        try {
          const parsed = JSON.parse(raw) as GameStreamEvent;
          return parsed;
        } catch {
          return null;
        }
      };

      const emitErrorEvent = (message: string): void => {
        const errorEvent: ErrorEvent = {
          type: 'error',
          error: { code: 'STREAM_ERROR', message }
        };
        observer.next(errorEvent);
      };

      const startNdjsonFallback = async (): Promise<void> => {
        if (fallbackActive) {
          return;
        }

        fallbackActive = true;
        console.log(`[GameApiService] NDJSON fallback started for ${gameId}, URL:`, streamUrl);

        try {
          const response = await fetch(streamUrl, {
            headers: { Accept: 'application/x-ndjson' },
            signal: abortController.signal
          });

          if (!response.ok || !response.body) {
            console.error(`[GameApiService] NDJSON fetch failed: HTTP ${response.status}`);
            emitErrorEvent(`Unable to open stream: HTTP ${response.status}`);
            observer.complete();
            return;
          }

          console.log(`[GameApiService] NDJSON stream connected for ${gameId}`);
          const reader = response.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 });
            const lines = buffer.split('\n');
            buffer = lines.pop() ?? '';

            for (const line of lines) {
              const event = parseEvent(line);
              if (event) {
                observer.next(event);
              }
            }
          }

          observer.complete();
        } catch (error) {
          if ((error as Error).name !== 'AbortError') {
            emitErrorEvent((error as Error).message);
            observer.error(error);
          }
        }
      };

      ws.onopen = () => {
        connected = true;
      };

      ws.onmessage = (message) => {
        const payload = typeof message.data === 'string' ? message.data : '';
        const event = parseEvent(payload);
        if (event) {
          observer.next(event);
        }
      };

      ws.onerror = (error) => {
        console.warn(`[GameApiService] WebSocket error for ${gameId}, attempting NDJSON fallback:`, error);
        if (!connected) {
          void startNdjsonFallback();
        }
      };

      ws.onclose = () => {
        console.warn(`[GameApiService] WebSocket closed for ${gameId}, connected=${connected}`);
        if (!connected) {
          console.log(`[GameApiService] Starting NDJSON fallback for ${gameId}`);
          void startNdjsonFallback();
        } else {
          observer.complete();
        }
      };

      return () => {
        abortController.abort();
        ws.close();
      };
    });
  }
}
