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';
import { GameHistoryService } from '../../services/game-history.service';

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

  private fenHistory: string[] = [];
  private sessionStartPly = 0;
  viewingPly: number | null = null;

  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);
  private readonly gameHistory = inject(GameHistoryService);

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

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

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

  get displayFen(): string {
    if (this.viewingPly !== null) {
      const historyIndex = this.viewingPly - this.sessionStartPly;
      return this.fenHistory[historyIndex] ?? this.game?.state.fen ?? '';
    }
    return this.game?.state.fen ?? '';
  }

  get isReviewing(): boolean {
    return this.viewingPly !== null;
  }

  navigateToPly(ply: number): void {
    const historyIndex = ply - this.sessionStartPly;
    if (historyIndex < 0 || historyIndex >= this.fenHistory.length) return;
    this.viewingPly = ply;
    this.boardSelection = this.boardSelectionService.clearSelection();
  }

  navigateHistory(direction: 'first' | 'prev' | 'next' | 'last'): void {
    const totalPly = this.sessionStartPly + this.fenHistory.length - 1;
    const current = this.viewingPly ?? totalPly;

    let next: number;
    switch (direction) {
      case 'first': next = this.sessionStartPly; break;
      case 'prev':  next = Math.max(this.sessionStartPly, current - 1); break;
      case 'next':  next = Math.min(totalPly, current + 1); break;
      case 'last':
      default:      next = totalPly; break;
    }

    if (next === totalPly) {
      this.viewingPly = null;
    } else {
      this.viewingPly = next;
    }
    this.boardSelection = this.boardSelectionService.clearSelection();
  }

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

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

  onBoardSquareSelected(square: string): void {
    if (!this.state || this.viewingPly !== null) {
      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.clockSyncedAt = Date.now();
            this.pushFen(state.fen);
            this.viewingPly = null;
            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 = [];
  }

  requestResign(): void {
    this.resignConfirmPending = true;
  }

  cancelResign(): void {
    this.resignConfirmPending = false;
  }

  confirmResign(): void {
    this.resignConfirmPending = false;
    this.gameApi
      .resignGame(this.gameId)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe({
        error: (error) => {
          this.errorMessage = getErrorMessage(error, 'Could not resign.');
        }
      });
  }

  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.fenHistory = [];
    this.viewingPly = null;

    this.gameApi
      .getGame(this.gameId)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe({
        next: (game) => {
          this.game = game;
          this.clockSyncedAt = Date.now();
          this.loading = false;
          this.sessionStartPly = game.state.moves.length;
          this.fenHistory = [game.state.fen];
          this.updateGameCompletion();
          this.gameHistory.recordGame(this.gameId);
          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),
      () => { /* polling fallback — not an error */ }
    );
  }

  private applyStreamEvent(event: GameStreamEvent): void {
    if (event.type === 'gameFull') {
      this.game = event.game;
      this.clockSyncedAt = Date.now();
      this.pushFen(event.game.state.fen);
      if (this.viewingPly === null) {
        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.clockSyncedAt = Date.now();
      this.pushFen(event.state.fen);
      this.updateGameCompletion();
      if (event.state.moves.length !== moveCountBefore && this.viewingPly === null) {
        this.boardSelection = this.boardSelectionService.clearSelection();
        this.tryMakeBotMove();
      }
      return;
    }

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

  private pushFen(fen: string): void {
    const last = this.fenHistory[this.fenHistory.length - 1];
    if (last !== fen) {
      this.fenHistory.push(fen);
    }
  }

  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;
  }
}
