import { DestroyRef, Injectable, OnDestroy, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { getErrorMessage } from '../../core/http/error-message.util';
import { GameFull, GameState, GameStreamEvent, LegalMove } from '../../models/game.models';
import { GameApiService } from '../../services/game-api.service';
import { BotMoveService } from '../../services/bot-move.service';
import { GameCompletionService } from '../../services/game-completion.service';
import { GameImportService } from '../../services/game-import.service';
import { BoardSelectionService, BoardSelection } from '../../services/board-selection.service';
import { GameStreamService } from '../../services/game-stream.service';

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

  private boardSelection: BoardSelection = {
    selectedSquare: null,
    highlightedSquares: [],
    selectedSquareMoves: []
  };
  private pendingPromotionMoves: LegalMove[] = [];

  private readonly gameApi = inject(GameApiService);
  private readonly destroyRef = inject(DestroyRef);
  private readonly botMoveService = inject(BotMoveService);
  private readonly completionService = inject(GameCompletionService);
  private readonly importService = inject(GameImportService);
  private readonly boardSelectionService = inject(BoardSelectionService);
  private readonly streamService = inject(GameStreamService);

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

  get selectedSquare(): string | null {
    return this.boardSelection.selectedSquare;
  }

  get highlightedSquares(): string[] {
    return this.boardSelection.highlightedSquares;
  }

  ngOnDestroy(): void {
    this.streamService.cleanup();
    this.botMoveService.cleanup();
  }

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

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

    // Handle move selection
    if (this.boardSelection.selectedSquare && this.boardSelection.highlightedSquares.includes(square)) {
      const selectedMove = this.boardSelection.selectedSquareMoves.find((move) => move.to === square);
      if (selectedMove) {
        // If multiple promotion outcomes exist for the target, ask player to choose one.
        const promotionMoves = this.boardSelection.selectedSquareMoves.filter(
          (move) => move.to === square && !!move.promotion
        );
        if (promotionMoves.length > 0) {
          this.pendingPromotionMoves = promotionMoves;
          this.isPromotionDialogOpen = true;
          return;
        }
        this.moveInput = selectedMove.uci;
        this.submitMove();
      }
      return;
    }

    // Load moves for selected square
    this.errorMessage = '';
    const newSelection = this.boardSelectionService.handleSquareSelection(
      square,
      this.gameId,
      this.state,
      this.boardSelection,
      (moves) => {
        this.boardSelection = {
          selectedSquare: square,
          highlightedSquares: moves.map((move) => move.to),
          selectedSquareMoves: moves
        };
      },
      (error) => {
        this.errorMessage = error;
        this.boardSelection = this.boardSelectionService.clearSelection();
      }
    );
    this.boardSelection = newSelection;
  }

  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.updateGameCompletion();
          }
          this.moveInput = '';
          this.boardSelection = this.boardSelectionService.clearSelection();
          this.botMoveService.tryMakeBotMove(
            this.gameId,
            this.game,
            this.state,
            (updatedState) => {
              if (this.game) {
                this.game = { ...this.game, state: updatedState };
                this.updateGameCompletion();
              }
              this.boardSelection = this.boardSelectionService.clearSelection();
            },
            (error) => {
              this.errorMessage = error;
            }
          );
        },
        error: (error) => {
          this.errorMessage = getErrorMessage(error, 'Move rejected.');
        }
      });
  }

  onPromotionSelected(promotionPiece: 'queen' | 'rook' | 'bishop' | 'knight'): void {
    const selectedPromotionMove = this.pendingPromotionMoves.find((move) => move.promotion === promotionPiece);
    if (!selectedPromotionMove) {
      this.errorMessage = 'Selected promotion move is unavailable.';
      this.isPromotionDialogOpen = false;
      this.pendingPromotionMoves = [];
      return;
    }

    this.moveInput = selectedPromotionMove.uci;
    this.isPromotionDialogOpen = false;
    this.boardSelection = this.boardSelectionService.clearSelection();
    this.pendingPromotionMoves = [];
    this.submitMove();
  }

  onPromotionClosed(): void {
    this.isPromotionDialogOpen = false;
    this.boardSelection = this.boardSelectionService.clearSelection();
    this.pendingPromotionMoves = [];
  }

  importFen(): void {
    this.errorMessage = '';
    this.importService.importFen(
      this.fenInput,
      () => {
        this.fenInput = '';
        this.pgnInput = '';
      },
      (error) => {
        this.errorMessage = error;
      }
    );
  }

  importPgn(): void {
    this.errorMessage = '';
    this.importService.importPgn(
      this.pgnInput,
      () => {
        this.pgnInput = '';
        this.fenInput = '';
      },
      (error) => {
        this.errorMessage = error;
      }
    );
  }

  private loadGame(): void {
    this.loading = true;
    this.errorMessage = '';
    this.boardSelection = this.boardSelectionService.clearSelection();
    this.streamService.cleanup();

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

  private startStreaming(): void {
    this.streamService.startStreaming(
      this.gameId,
      (event) => this.applyStreamEvent(event),
      () => {
        this.errorMessage = 'Live stream disconnected. Falling back to polling.';
      }
    );
  }

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

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

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

  private tryMakeBotMove(): void {
    this.botMoveService.tryMakeBotMove(
      this.gameId,
      this.game,
      this.state,
      (updatedState) => {
        if (this.game) {
          this.game = { ...this.game, state: updatedState };
          this.updateGameCompletion();
        }
        this.boardSelection = this.boardSelectionService.clearSelection();
      },
      (error) => {
        this.errorMessage = error;
      }
    );
  }

  private updateGameCompletion(): void {
    const completion = this.completionService.getGameCompletion(this.game, this.state);
    this.gameCompletionMessage = completion.message;
    this.isGameFinished = completion.isFinished;
  }
}
