import { DestroyRef, Injectable, OnDestroy, inject } from '@angular/core';
import { Router } from '@angular/router';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { interval, startWith, Subscription, switchMap, delay } from 'rxjs';
import { getPieceAtSquare, isPieceColor } from '../../core/chess/fen.utils';
import { getErrorMessage } from '../../core/http/error-message.util';
import { GameFull, GameState, GameStreamEvent, LegalMove } from '../../models/game.models';
import { GameApiService } from '../../services/game-api.service';

@Injectable()
export class GameFacade implements OnDestroy {
  gameId = '';
  game: GameFull | null = null;
  errorMessage = '';
  moveInput = '';
  fenInput = '';
  pgnInput = '';
  loading = true;
  selectedSquare: string | null = null;
  highlightedSquares: string[] = [];
  gameCompletionMessage = '';
  isGameFinished = false;

  private selectedSquareMoves: LegalMove[] = [];
  private readonly router = inject(Router);
  private readonly gameApi = inject(GameApiService);
  private readonly destroyRef = inject(DestroyRef);
  private streamSubscription: Subscription | null = null;
  private pollSubscription: Subscription | null = null;
  private botMoveSubscription: Subscription | null = null;

  private getGameCompletionMessage(): void {
    if (!this.game || !this.state) {
      this.gameCompletionMessage = '';
      this.isGameFinished = false;
      return;
    }

    const status = this.state.status;
    const gameEndingStatuses = ['checkmate', 'stalemate', 'resign', 'draw', 'insufficientMaterial'];

    if (!gameEndingStatuses.includes(status)) {
      this.gameCompletionMessage = '';
      this.isGameFinished = false;
      return;
    }

    this.isGameFinished = true;

    switch (status) {
      case 'checkmate':
        const winner = this.state.winner === 'white' ? this.game.white.displayName : this.game.black.displayName;
        this.gameCompletionMessage = `Checkmate! ${winner} wins!`;
        break;
      case 'stalemate':
        this.gameCompletionMessage = 'Stalemate! The game is a draw.';
        break;
      case 'resign':
        const resignedPlayer = this.state.winner === 'white' ? this.game.black.displayName : this.game.white.displayName;
        const resignedWinner = this.state.winner === 'white' ? this.game.white.displayName : this.game.black.displayName;
        this.gameCompletionMessage = `${resignedPlayer} resigned. ${resignedWinner} wins!`;
        break;
      case 'draw':
        this.gameCompletionMessage = 'Draw! The game ended in a draw.';
        break;
      case 'insufficientMaterial':
        this.gameCompletionMessage = 'Insufficient material! The game is a draw.';
        break;
      default:
        this.gameCompletionMessage = 'Game ended!';
    }
  }

  ngOnDestroy(): void {
    this.streamSubscription?.unsubscribe();
    this.pollSubscription?.unsubscribe();
    this.botMoveSubscription?.unsubscribe();
  }

  get state(): GameState | null {
    return this.game?.state ?? null;
  }

  private isBotPlayer(playerId: string): boolean {
    return playerId.startsWith('bot-');
  }

  private isPlayingAgainstBot(): boolean {
    if (!this.game) {
      return false;
    }
    return this.isBotPlayer(this.game.white.id) || this.isBotPlayer(this.game.black.id);
  }

  private isCurrentPlayerBot(): boolean {
    if (!this.game || !this.state) {
      return false;
    }
    const currentPlayer = this.state.turn === 'white' ? this.game.white : this.game.black;
    return this.isBotPlayer(currentPlayer.id);
  }

  setGameId(gameId: string): void {
    this.gameId = gameId;
    this.loadGame();
  }

  onBoardSquareSelected(square: string): void {
    if (!this.state) {
      return;
    }

    if (this.selectedSquare && this.highlightedSquares.includes(square)) {
      const selectedMove = this.selectedSquareMoves.find((move) => move.to === square);
      if (selectedMove) {
        this.moveInput = selectedMove.uci;
        this.submitMove();
      }
      return;
    }

    const piece = getPieceAtSquare(this.state.fen, square);
    if (!piece || !isPieceColor(piece, this.state.turn)) {
      this.clearSelection();
      return;
    }

    this.errorMessage = '';
    this.gameApi
      .getLegalMoves(this.gameId, square)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe({
        next: (response) => {
          this.selectedSquare = square;
          this.selectedSquareMoves = response.moves;
          this.highlightedSquares = response.moves.map((move) => move.to);
        },
        error: () => {
          this.clearSelection();
          this.errorMessage = 'Could not load legal moves for selected square.';
        }
      });
  }

  submitMove(): void {
    const uci = this.moveInput.trim();
    if (!uci) {
      return;
    }

    this.errorMessage = '';
    this.gameApi
      .makeMove(this.gameId, uci)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe({
        next: (state) => {
          if (this.game) {
            this.game = { ...this.game, state };
          }
          this.moveInput = '';
          this.clearSelection();
          this.tryMakeBotMove();
        },
        error: (error) => {
          this.errorMessage = getErrorMessage(error, 'Move rejected.');
        }
      });
  }

  importFen(): void {
    const fen = this.fenInput.trim();
    if (!fen) {
      this.errorMessage = 'Please provide a FEN string.';
      return;
    }

    this.errorMessage = '';
    this.gameApi
      .importFen(fen)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe({
        next: (game) => {
          this.fenInput = '';
          this.pgnInput = '';
          void this.router.navigate(['/game', game.gameId]);
        },
        error: (error) => {
          this.errorMessage = getErrorMessage(error, 'FEN import failed.');
        }
      });
  }

  importPgn(): void {
    const pgn = this.pgnInput.trim();
    if (!pgn) {
      this.errorMessage = 'Please provide a PGN string.';
      return;
    }

    this.errorMessage = '';
    this.gameApi
      .importPgn(pgn)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe({
        next: (game) => {
          this.pgnInput = '';
          this.fenInput = '';
          void this.router.navigate(['/game', game.gameId]);
        },
        error: (error) => {
          this.errorMessage = getErrorMessage(error, 'PGN import failed.');
        }
      });
  }

  private loadGame(): void {
    this.loading = true;
    this.errorMessage = '';
    this.clearSelection();
    this.streamSubscription?.unsubscribe();
    this.pollSubscription?.unsubscribe();
    this.botMoveSubscription?.unsubscribe();
    this.pollSubscription = null;

    this.gameApi
      .getGame(this.gameId)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe({
        next: (game) => {
          this.game = game;
          this.loading = false;
          this.getGameCompletionMessage();
          this.startStream();
          this.tryMakeBotMove();
        },
        error: (error) => {
          this.errorMessage = getErrorMessage(error, `Could not load game ${this.gameId}.`);
          this.loading = false;
        }
      });
  }

  private startStream(): void {
    this.streamSubscription = this.gameApi
      .streamGame(this.gameId)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe({
        next: (event) => this.applyStreamEvent(event),
        error: () => {
          this.errorMessage = 'Live stream disconnected. Falling back to polling.';
          this.startPolling();
        },
        complete: () => {
          this.errorMessage = 'Live stream ended. Falling back to polling.';
          this.startPolling();
        }
      });
  }

  private startPolling(): void {
    if (this.pollSubscription) {
      return;
    }

    this.pollSubscription = interval(1500)
      .pipe(
        startWith(0),
        switchMap(() => this.gameApi.getGame(this.gameId)),
        takeUntilDestroyed(this.destroyRef)
      )
      .subscribe({
        next: (game) => {
          const previousMoves = this.game?.state.moves.join(',') ?? '';
          this.game = game;
          this.getGameCompletionMessage();
          if (previousMoves !== game.state.moves.join(',')) {
            this.clearSelection();
            this.tryMakeBotMove();
          }
        }
      });
  }

  private applyStreamEvent(event: GameStreamEvent): void {
    if (event.type === 'gameFull') {
      this.game = event.game;
      this.clearSelection();
      this.getGameCompletionMessage();
      this.tryMakeBotMove();
      return;
    }

    if (event.type === 'gameState' && this.game) {
      const moveCountBefore = this.game.state.moves.length;
      this.game = { ...this.game, state: event.state };
      this.getGameCompletionMessage();
      if (event.state.moves.length !== moveCountBefore) {
        this.clearSelection();
        this.tryMakeBotMove();
      }
      return;
    }

    if (event.type === 'error') {
      this.errorMessage = event.error.message;
    }
  }

  private tryMakeBotMove(): void {
    if (!this.isPlayingAgainstBot() || !this.isCurrentPlayerBot() || !this.state) {
      return;
    }

    this.botMoveSubscription?.unsubscribe();
    this.botMoveSubscription = this.gameApi
      .getLegalMoves(this.gameId)
      .pipe(
        delay(1000),
        takeUntilDestroyed(this.destroyRef)
      )
      .subscribe({
        next: (response) => {
          if (response.moves.length === 0) {
            return;
          }
          const botMove = response.moves[Math.floor(Math.random() * response.moves.length)];
          this.gameApi
            .makeMove(this.gameId, botMove.uci)
            .pipe(takeUntilDestroyed(this.destroyRef))
            .subscribe({
              next: (state) => {
                if (this.game) {
                  this.game = { ...this.game, state };
                }
                this.clearSelection();
              },
              error: (error) => {
                this.errorMessage = getErrorMessage(error, 'Bot move failed.');
              }
            });
        },
        error: () => {
          this.errorMessage = 'Could not get legal moves for bot move.';
        }
      });
  }

  private clearSelection(): void {
    this.selectedSquare = null;
    this.selectedSquareMoves = [];
    this.highlightedSquares = [];
  }
}
