import { Chess, Move } from 'chess.js';
import { OpeningMetadata } from '@/lib/openings';
import { StockfishEvaluation } from '@/lib/stockfish';
import {
  MoveHistoryEntry,
  MoveCategory,
  MoveFeedbackClassification
} from '@/types/openingTraining';
import { STARTING_FEN, MOVE_CATEGORIZATION_THRESHOLDS } from './constants';

/**
 * Pure Business Logic for Opening Training
 *
 * This module contains all the core chess logic WITHOUT any React dependencies.
 * All functions are pure (no side effects) and can be unit tested in isolation.
 */

// ============================================================================
// Move Validation & Execution
// ============================================================================

export interface MoveResult {
  san: string;
  uci: string;
  from: string;
  to: string;
  promotion?: string;
  color: 'w' | 'b';
  piece: string;
  captured?: string;
  flags: string;
}

/**
 * Validate and execute a move on a given position
 * Returns move details if legal, null if illegal
 *
 * PURE FUNCTION: Creates a temporary chess instance, does not mutate anything
 */
export function validateMove(fen: string, san: string): MoveResult | null {
  const chess = new Chess(fen);

  try {
    const move = chess.move(san);
    if (!move) return null;

    return {
      san: move.san,
      uci: move.from + move.to + (move.promotion || ''),
      from: move.from,
      to: move.to,
      promotion: move.promotion,
      color: move.color,
      piece: move.piece,
      captured: move.captured,
      flags: move.flags,
    };
  } catch (error) {
    // chess.move() can throw on invalid input
    return null;
  }
}

/**
 * Apply a move to a position and return the new FEN
 *
 * PURE FUNCTION: Creates a new chess instance, does not mutate anything
 */
export function applyMove(fen: string, san: string): { newFen: string; move: MoveResult } | null {
  const moveResult = validateMove(fen, san);
  if (!moveResult) return null;

  const chess = new Chess(fen);
  chess.move(san);

  return {
    newFen: chess.fen(),
    move: moveResult,
  };
}

/**
 * Build FEN by undoing moves to a specific index in history
 * Returns STARTING_FEN if targetIndex is 0
 *
 * PURE FUNCTION: Replays moves from scratch
 */
export function buildFenAtIndex(moveHistory: MoveHistoryEntry[], targetIndex: number): string {
  if (targetIndex === 0) return STARTING_FEN;
  if (targetIndex > moveHistory.length) return STARTING_FEN;

  // Use the FEN stored in the move history
  // moveHistory[0] is the FEN after the 1st move
  // moveHistory[1] is the FEN after the 2nd move, etc.
  return moveHistory[targetIndex - 1].fen;
}

// ============================================================================
// Opening Theory Logic
// ============================================================================

/**
 * Parse a move sequence from an opening's moves string
 * Example: "1. e4 e5 2. Nf3" → ["e4", "e5", "Nf3"]
 */
export function parseMoveSequence(moveString: string): string[] {
  if (!moveString || moveString.trim() === '') return [];

  const cleaned = moveString
    .replace(/\d+\./g, '') // Remove move numbers
    .replace(/\s+/g, ' ') // Normalize whitespace
    .trim();

  return cleaned.split(' ').filter((move) => move.length > 0);
}

/**
 * Check if the current position is still within opening theory
 *
 * @param opening - Opening metadata
 * @param moveHistory - Moves played so far
 * @param proposedMove - Optional: also check if this next move would be in theory
 */
export function isInTheory(
  opening: OpeningMetadata,
  moveHistory: MoveHistoryEntry[],
  proposedMove?: string
): boolean {
  const repertoireMoves = parseMoveSequence(opening.moves);
  const totalMoves = proposedMove
    ? moveHistory.length + 1
    : moveHistory.length;

  // If we've played more moves than the repertoire has, we're past theory
  if (totalMoves > repertoireMoves.length) {
    return false;
  }

  // Check that all played moves match the repertoire
  for (let i = 0; i < moveHistory.length; i++) {
    if (moveHistory[i].san !== repertoireMoves[i]) {
      return false;
    }
  }

  // If checking a proposed move, verify it matches too
  if (proposedMove && totalMoves <= repertoireMoves.length) {
    return proposedMove === repertoireMoves[moveHistory.length];
  }

  return true;
}

/**
 * Get the expected next move(s) from the repertoire at current position
 * For now, returns single main line move (no variants yet)
 * Future: could return multiple moves for different variations
 */
export function getExpectedNextMoves(
  opening: OpeningMetadata,
  currentMoveIndex: number
): string[] {
  const moves = parseMoveSequence(opening.moves);

  if (currentMoveIndex >= moves.length) {
    return [];
  }

  const nextMove = moves[currentMoveIndex];
  return nextMove ? [nextMove] : [];
}

/**
 * Get all variant moves at current position
 * For MVP: same as getExpectedNextMoves (no variants stored in database yet)
 * Future: Parse variation syntax like "1. e4 (1. d4) e5"
 */
export function getAllVariantMoves(
  opening: OpeningMetadata,
  currentMoveIndex: number
): string[] {
  // MVP: No variant support yet, just return main line
  return getExpectedNextMoves(opening, currentMoveIndex);
}

/**
 * Detect if the current FEN has transposed into a different known opening
 * Future implementation: Would query opening database by FEN
 */
export function detectTransposition(
  fen: string,
  allOpenings: OpeningMetadata[]
): { opening: OpeningMetadata; moveIndex: number } | null {
  // TODO: Implement FEN-based opening lookup
  // For now, return null (no transposition detection)
  return null;
}

/**
 * Check if we've reached the end of the opening repertoire
 */
export function isEndOfRepertoire(
  opening: OpeningMetadata,
  currentMoveIndex: number
): boolean {
  const moves = parseMoveSequence(opening.moves);
  return currentMoveIndex >= moves.length;
}

// ============================================================================
// Move Classification
// ============================================================================

/**
 * Classify a user's move based on repertoire matching and engine evaluation
 */
export function classifyMove(
  san: string,
  expectedMoves: string[],
  variantMoves: string[],
  evaluation: StockfishEvaluation,
  previousEvaluation: StockfishEvaluation
): {
  category: MoveCategory;
  evaluationChange: number;
  theoreticalAlternatives: string[];
  isTransposition: boolean;
  newOpening?: OpeningMetadata;
} {
  const isInRepertoire = expectedMoves.includes(san);
  const isVariant = !isInRepertoire && variantMoves.includes(san);

  // Calculate evaluation change (White's perspective)
  const evalChange = evaluation.score - previousEvaluation.score;

  // Determine category
  let category: MoveCategory;

  if (isInRepertoire) {
    category = 'in-theory';
  } else {
    // Not in main repertoire - check if weak or playable
    const cpLoss = Math.abs(evalChange);

    if (cpLoss >= MOVE_CATEGORIZATION_THRESHOLDS.WEAK_MOVE_CP_LOSS) {
      category = 'weak';
    } else {
      category = 'playable';
    }
  }

  // Get theoretical alternatives (excluding the move just played)
  const alternatives = expectedMoves.filter(move => move !== san);

  return {
    category,
    evaluationChange: evalChange,
    theoreticalAlternatives: alternatives,
    isTransposition: false, // TODO: Implement transposition detection
  };
}

// ============================================================================
// Turn & Color Logic
// ============================================================================

/**
 * Determine which color the user is playing based on ECO code
 * A, B, C = White openings (user plays White)
 * D, E = Black defenses (user plays Black)
 */
export function getUserColor(opening: OpeningMetadata): 'white' | 'black' {
  const ecoLetter = opening.eco[0];
  return ['A', 'B', 'C'].includes(ecoLetter) ? 'white' : 'black';
}

/**
 * Check if it's the user's turn to move
 */
export function isUserTurn(opening: OpeningMetadata, moveHistory: MoveHistoryEntry[]): boolean {
  const userColor = getUserColor(opening);
  const moveIndex = moveHistory.length;
  const isWhiteMove = moveIndex % 2 === 0;

  return (userColor === 'white' && isWhiteMove) ||
         (userColor === 'black' && !isWhiteMove);
}

/**
 * Check if it's the opponent's turn to move
 */
export function isOpponentTurn(opening: OpeningMetadata, moveHistory: MoveHistoryEntry[]): boolean {
  return !isUserTurn(opening, moveHistory);
}

/**
 * Determine if opponent should automatically make a move
 * Opponent auto-moves only if:
 * 1. It's their turn
 * 2. We're still in theory
 * 3. We haven't reached end of repertoire
 */
export function shouldOpponentAutoMove(
  opening: OpeningMetadata,
  moveHistory: MoveHistoryEntry[],
  deviationMoveIndex: number | null
): boolean {
  // If user has deviated from theory, no auto-moves
  if (deviationMoveIndex !== null) {
    return false;
  }

  // If at end of repertoire, no auto-moves
  if (isEndOfRepertoire(opening, moveHistory.length)) {
    return false;
  }

  // Must be opponent's turn
  if (!isOpponentTurn(opening, moveHistory)) {
    return false;
  }

  // Must still be in theory
  if (!isInTheory(opening, moveHistory)) {
    return false;
  }

  return true;
}

/**
 * Get the opponent's next move from the repertoire
 */
export function getOpponentNextMove(
  opening: OpeningMetadata,
  currentMoveIndex: number
): string | null {
  const moves = parseMoveSequence(opening.moves);

  if (currentMoveIndex >= moves.length) {
    return null;
  }

  return moves[currentMoveIndex];
}

// ============================================================================
// Deviation Tracking
// ============================================================================

/**
 * Find the index where the user first deviated from theory
 * Returns null if still in theory
 */
export function findDeviationPoint(
  opening: OpeningMetadata,
  moveHistory: MoveHistoryEntry[]
): number | null {
  const repertoireMoves = parseMoveSequence(opening.moves);

  for (let i = 0; i < moveHistory.length; i++) {
    // Skip opponent moves (we only care about user deviations)
    const userColor = getUserColor(opening);
    const moveColor = moveHistory[i].color;
    const isUserMove =
      (userColor === 'white' && moveColor === 'white') ||
      (userColor === 'black' && moveColor === 'black');

    if (!isUserMove) continue;

    // Check if this move matches theory
    if (i >= repertoireMoves.length || moveHistory[i].san !== repertoireMoves[i]) {
      return i;
    }
  }

  return null;
}

/**
 * Reset deviation tracking if user navigated back and is now in theory again
 *
 * @param currentDeviation - Current deviation index (or null)
 * @param navigationIndex - Index user navigated to
 * @param newMove - New move user is about to make
 * @param opening - Opening metadata
 * @param moveHistory - Current move history (before new move)
 * @returns Updated deviation index (null if back in theory)
 */
export function resetDeviationIfBackInTheory(
  currentDeviation: number | null,
  navigationIndex: number,
  newMove: string,
  opening: OpeningMetadata,
  moveHistory: MoveHistoryEntry[]
): number | null {
  // If we navigated before the deviation point
  if (currentDeviation !== null && navigationIndex < currentDeviation) {
    // Check if the new move is in theory
    if (isInTheory(opening, moveHistory, newMove)) {
      // Back in theory! Reset deviation
      return null;
    }
  }

  return currentDeviation;
}

// ============================================================================
// Wikipedia Integration
// ============================================================================

/**
 * Extract the family name from a full opening name
 * Example: "French Defense: Winawer Variation, Advance" → "French Defense"
 */
export function extractOpeningFamily(openingName: string): string {
  // Split on colon to get the main family
  const parts = openingName.split(':');
  return parts[0].trim();
}

/**
 * Check if an opening has Wikipedia data available
 */
export function hasWikipediaPage(opening: OpeningMetadata): boolean {
  return !!opening.wikipediaSlug;
}

/**
 * Determine if Wikipedia context should be included in LLM prompts
 */
export function shouldUseWikipediaContext(opening: OpeningMetadata): boolean {
  // Use Wikipedia if available and not too obscure
  return hasWikipediaPage(opening);
}
