from __future__ import annotations

import dataclasses
from collections.abc import Callable

import chess
import chess.engine
import chess.pgn

from chess_cli.board_render import (
    BOLD,
    GREEN,
    RED,
    YELLOW,
    ORANGE,
    WHITE_TEXT,
    RESET,
    DIM,
)

CLASSIFICATIONS = [
    ("Best", 0),
    ("Excellent", 10),
    ("Good", 30),
    ("Inaccuracy", 80),
    ("Mistake", 200),
    ("Blunder", float("inf")),
]

CLASSIFICATION_COLORS = {
    "Best": GREEN,
    "Excellent": GREEN,
    "Good": WHITE_TEXT,
    "Inaccuracy": YELLOW,
    "Mistake": ORANGE,
    "Blunder": RED,
}

CLASSIFICATION_SYMBOLS = {
    "Best": "!!",
    "Excellent": "!",
    "Good": "",
    "Inaccuracy": "?!",
    "Mistake": "?",
    "Blunder": "??",
}


@dataclasses.dataclass
class MoveAnalysis:
    move_number: int
    color: chess.Color
    san: str
    eval_before: float  # centipawns from white's perspective
    eval_after: float
    best_move_san: str
    best_eval: float
    cp_loss: int
    classification: str


def classify_move(cp_loss: int) -> str:
    for name, threshold in CLASSIFICATIONS:
        if cp_loss <= threshold:
            return name
    return "Blunder"


def analyze_game(
    pgn_game: chess.pgn.Game,
    engine: chess.engine.SimpleEngine,
    depth: int = 20,
    progress_callback: Callable[[int, int], None] | None = None,
) -> list[MoveAnalysis]:
    moves = list(pgn_game.mainline_moves())
    total = len(moves)
    results: list[MoveAnalysis] = []
    board = chess.Board()

    # Analyze initial position
    info = engine.analyse(board, chess.engine.Limit(depth=depth))
    prev_eval_cp = _score_to_cp(info["score"].white())
    prev_best_move = info.get("pv", [None])[0]

    for i, move in enumerate(moves):
        move_number = (i // 2) + 1
        color = board.turn

        # The eval and best move for THIS position were computed in the previous iteration
        eval_before = prev_eval_cp
        best_move = prev_best_move
        best_move_san = board.san(best_move) if best_move else "?"

        # Record the SAN of the actual move
        san = board.san(move)

        # Push the actual move
        board.push(move)

        # Analyze the position AFTER the move
        info = engine.analyse(board, chess.engine.Limit(depth=depth))
        eval_after = _score_to_cp(info["score"].white())
        next_best_move = info.get("pv", [None])[0]

        # Compute centipawn loss
        if color == chess.WHITE:
            cp_loss = max(0, eval_before - eval_after)
        else:
            cp_loss = max(0, eval_after - eval_before)

        classification = classify_move(cp_loss)

        results.append(MoveAnalysis(
            move_number=move_number,
            color=color,
            san=san,
            eval_before=eval_before / 100.0,
            eval_after=eval_after / 100.0,
            best_move_san=best_move_san,
            best_eval=eval_before / 100.0,
            cp_loss=cp_loss,
            classification=classification,
        ))

        prev_eval_cp = eval_after
        prev_best_move = next_best_move

        if progress_callback:
            progress_callback(i + 1, total)

    return results


def _score_to_cp(score: chess.engine.PovScore) -> int:
    """Convert a score to centipawns, using a large value for mate."""
    if score.is_mate():
        mate = score.mate()
        return 100000 if mate > 0 else -100000
    return score.score(mate_score=100000)


def format_analysis_report(
    analysis: list[MoveAnalysis],
    white_name: str,
    black_name: str,
) -> str:
    lines: list[str] = []
    lines.append(f"\n  {BOLD}=== Game Analysis ==={RESET}\n")
    lines.append(f"  White: {white_name}    Black: {black_name}\n")

    # Move-by-move
    lines.append(f"  {BOLD}Move-by-move:{RESET}")
    i = 0
    while i < len(analysis):
        entry = analysis[i]
        color = CLASSIFICATION_COLORS.get(entry.classification, WHITE_TEXT)
        symbol = CLASSIFICATION_SYMBOLS.get(entry.classification, "")

        if entry.color == chess.WHITE:
            loss_str = f"+{entry.cp_loss}cp" if entry.cp_loss > 0 else ""
            white_part = f"{color}{entry.move_number}. {entry.san:<7} [{entry.classification}{' ' + loss_str if loss_str else ''}]{RESET}"

            # Check if there's a corresponding black move
            black_part = ""
            if i + 1 < len(analysis) and analysis[i + 1].color == chess.BLACK:
                b = analysis[i + 1]
                b_color = CLASSIFICATION_COLORS.get(b.classification, WHITE_TEXT)
                b_loss = f"+{b.cp_loss}cp" if b.cp_loss > 0 else ""
                black_part = f"  {b_color}...{b.san:<7} [{b.classification}{' ' + b_loss if b_loss else ''}]{RESET}"
                i += 1

            lines.append(f"    {white_part}{black_part}")

            # Show best move if not best
            if entry.classification not in ("Best", "Excellent", "Good"):
                lines.append(f"      {DIM}Best was: {entry.best_move_san}{RESET}")
            if i < len(analysis) and black_part and analysis[i].classification not in ("Best", "Excellent", "Good"):
                lines.append(f"      {DIM}Best was: ...{analysis[i].best_move_san}{RESET}")
        else:
            # Black move without preceding white (unusual, but handle it)
            b_color = CLASSIFICATION_COLORS.get(entry.classification, WHITE_TEXT)
            b_loss = f"+{entry.cp_loss}cp" if entry.cp_loss > 0 else ""
            lines.append(f"    {b_color}{entry.move_number}. ...{entry.san:<7} [{entry.classification}{' ' + b_loss if b_loss else ''}]{RESET}")
            if entry.classification not in ("Best", "Excellent", "Good"):
                lines.append(f"      {DIM}Best was: ...{entry.best_move_san}{RESET}")

        i += 1

    # Summary
    lines.append(f"\n  {BOLD}Summary:{RESET}")
    white_moves = [m for m in analysis if m.color == chess.WHITE]
    black_moves = [m for m in analysis if m.color == chess.BLACK]

    lines.append(f"    {'':15} {'White':>8}  {'Black':>8}")
    for class_name, _ in CLASSIFICATIONS:
        w_count = sum(1 for m in white_moves if m.classification == class_name)
        b_count = sum(1 for m in black_moves if m.classification == class_name)
        color = CLASSIFICATION_COLORS.get(class_name, WHITE_TEXT)
        lines.append(f"    {color}{class_name + ':':<15}{RESET} {w_count:>8}  {b_count:>8}")

    # Accuracy
    w_acpl = sum(m.cp_loss for m in white_moves) / max(len(white_moves), 1)
    b_acpl = sum(m.cp_loss for m in black_moves) / max(len(black_moves), 1)
    w_accuracy = max(0.0, 100.0 - w_acpl / 3.0)
    b_accuracy = max(0.0, 100.0 - b_acpl / 3.0)

    lines.append("")
    lines.append(f"    {BOLD}{'Accuracy:':<15}{RESET} {w_accuracy:>7.1f}%  {b_accuracy:>7.1f}%")
    lines.append(f"    {DIM}{'ACPL:':<15} {w_acpl:>7.0f}     {b_acpl:>7.0f}{RESET}")

    return "\n".join(lines)


def render_progress(current: int, total: int, width: int = 30) -> None:
    fraction = current / max(total, 1)
    filled = int(fraction * width)
    bar = "\u2588" * filled + "\u2591" * (width - filled)
    print(f"\r  Analyzing: [{bar}] {current}/{total} moves", end="", flush=True)
