import chess
import time
import math
import random

class ComplexMaiaEngine:
    def __init__(self, skill_level=3):
        # Default skill_level = 3 (~1300 Elo). Max = 20
        self.skill_level = max(1, min(20, skill_level))
        self.nodes_visited = 0
        
        # Piece values for Evaluation
        self.PIECE_VALUES = {
            chess.PAWN: 100,
            chess.KNIGHT: 320,
            chess.BISHOP: 330,
            chess.ROOK: 500,
            chess.QUEEN: 900,
            chess.KING: 20000
        }

        # Piece-Square Tables (Heuristics for positional strength)
        self.PAWN_TABLE = [
            0,  0,  0,  0,  0,  0,  0,  0,
            50, 50, 50, 50, 50, 50, 50, 50,
            10, 10, 20, 30, 30, 20, 10, 10,
             5,  5, 10, 25, 25, 10,  5,  5,
             0,  0,  0, 20, 20,  0,  0,  0,
             5, -5,-10,  0,  0,-10, -5,  5,
             5, 10, 10,-20,-20, 10, 10,  5,
             0,  0,  0,  0,  0,  0,  0,  0
        ]

        self.KNIGHT_TABLE = [
            -50,-40,-30,-30,-30,-30,-40,-50,
            -40,-20,  0,  0,  0,  0,-20,-40,
            -30,  0, 10, 15, 15, 10,  0,-30,
            -30,  5, 15, 20, 20, 15,  5,-30,
            -30,  0, 15, 20, 20, 15,  0,-30,
            -30,  5, 10, 15, 15, 10,  5,-30,
            -40,-20,  0,  5,  5,  0,-20,-40,
            -50,-40,-30,-30,-30,-30,-40,-50
        ]

        self.BISHOP_TABLE = [
            -20,-10,-10,-10,-10,-10,-10,-20,
            -10,  0,  0,  0,  0,  0,  0,-10,
            -10,  0,  5, 10, 10,  5,  0,-10,
            -10,  5,  5, 10, 10,  5,  5,-10,
            -10,  0, 10, 10, 10, 10,  0,-10,
            -10, 10, 10, 10, 10, 10, 10,-10,
            -10,  5,  0,  0,  0,  0,  5,-10,
            -20,-10,-10,-10,-10,-10,-10,-20
        ]

        self.ROOK_TABLE = [
              0,  0,  0,  0,  0,  0,  0,  0,
              5, 10, 10, 10, 10, 10, 10,  5,
             -5,  0,  0,  0,  0,  0,  0, -5,
             -5,  0,  0,  0,  0,  0,  0, -5,
             -5,  0,  0,  0,  0,  0,  0, -5,
             -5,  0,  0,  0,  0,  0,  0, -5,
             -5,  0,  0,  0,  0,  0,  0, -5,
              0,  0,  0,  5,  5,  0,  0,  0
        ]

        self.QUEEN_TABLE = [
            -20,-10,-10, -5, -5,-10,-10,-20,
            -10,  0,  0,  0,  0,  0,  0,-10,
            -10,  0,  5,  5,  5,  5,  0,-10,
             -5,  0,  5,  5,  5,  5,  0, -5,
              0,  0,  5,  5,  5,  5,  0, -5,
            -10,  5,  5,  5,  5,  5,  0,-10,
            -10,  0,  5,  0,  0,  0,  0,-10,
            -20,-10,-10, -5, -5,-10,-10,-20
        ]

        self.KING_TABLE = [
            -30,-40,-40,-50,-50,-40,-40,-30,
            -30,-40,-40,-50,-50,-40,-40,-30,
            -30,-40,-40,-50,-50,-40,-40,-30,
            -30,-40,-40,-50,-50,-40,-40,-30,
            -20,-30,-30,-40,-40,-30,-30,-20,
            -10,-20,-20,-20,-20,-20,-20,-10,
             20, 20,  0,  0,  0,  0, 20, 20,
             20, 30, 10,  0,  0, 10, 30, 20
        ]

    def set_skill_level(self, level):
        self.skill_level = max(1, min(20, level))

    def evaluate_board(self, board):
        """ Statically evaluates the board layout. Positive = White advantage """
        if board.is_checkmate():
            if board.turn == chess.WHITE:
                return -99999  # Black wins
            else:
                return 99999   # White wins
        if board.is_stalemate() or board.is_insufficient_material():
            return 0

        score = 0
        
        for square in chess.SQUARES:
            piece = board.piece_at(square)
            if piece:
                piece_type = piece.piece_type
                value = self.PIECE_VALUES[piece_type]
                
                if self.skill_level < 5 and piece_type in [chess.KNIGHT, chess.BISHOP]:
                    value -= 15  

                table_score = 0
                square_idx = square if piece.color == chess.WHITE else chess.square_mirror(square)
                
                if piece_type == chess.PAWN: table_score = self.PAWN_TABLE[square_idx]
                elif piece_type == chess.KNIGHT: table_score = self.KNIGHT_TABLE[square_idx]
                elif piece_type == chess.BISHOP: table_score = self.BISHOP_TABLE[square_idx]
                elif piece_type == chess.ROOK: table_score = self.ROOK_TABLE[square_idx]
                elif piece_type == chess.QUEEN: table_score = self.QUEEN_TABLE[square_idx]
                elif piece_type == chess.KING: table_score = self.KING_TABLE[square_idx]

                # Fixed low-ELO blunder logic: 15% random chance to evaluate squares backward
                if self.skill_level <= 3 and random.random() < 0.15:
                    table_score = -table_score  

                if piece.color == chess.WHITE:
                    score += (value + table_score)
                else:
                    score -= (value + table_score)
        
        return score

    def order_moves(self, board, moves):
        scored_moves = []
        for move in moves:
            score = 0
            if board.is_capture(move):
                captured = board.piece_at(move.to_square)
                attacker = board.piece_at(move.from_square)
                if captured and attacker:
                    score += 10 * self.PIECE_VALUES[captured.piece_type] - self.PIECE_VALUES[attacker.piece_type]
            
            if move.promotion:
                score += 900
                
            if board.is_attacked_by(not board.turn, move.to_square):
                score -= self.PIECE_VALUES[board.piece_at(move.from_square).piece_type] if board.piece_at(move.from_square) else 50
                
            scored_moves.append((score, move))
            
        if self.skill_level > 8:
            scored_moves.sort(key=lambda x: x[0], reverse=True)
            
        return [move for score, move in scored_moves]

    def alpha_beta(self, board, depth, alpha, beta, maximizing_player):
        self.nodes_visited += 1
        
        if depth == 0 or board.is_game_over():
            return self.evaluate_board(board)

        legal_moves = self.order_moves(board, board.legal_moves)

        if maximizing_player:
            max_eval = -math.inf
            for move in legal_moves:
                board.push(move)
                evaluation = self.alpha_beta(board, depth - 1, alpha, beta, False)
                board.pop()
                max_eval = max(max_eval, evaluation)
                alpha = max(alpha, evaluation)
                if beta <= alpha:
                    break 
            return max_eval
        else:
            min_eval = math.inf
            for move in legal_moves:
                board.push(move)
                evaluation = self.alpha_beta(board, depth - 1, alpha, beta, True)
                board.pop()
                min_eval = min(min_eval, evaluation)
                beta = min(beta, evaluation)
                if beta <= alpha:
                    break 
            return min_eval

    def select_best_move(self, board):
        self.nodes_visited = 0
        best_move = None
        
        if self.skill_level <= 3:
            target_depth = 3
        elif self.skill_level <= 8:
            target_depth = 4
        else:
            target_depth = 5 

        legal_moves = self.order_moves(board, board.legal_moves)
        if not legal_moves:
            return None

        best_move = legal_moves[0]
        
        for current_depth in range(1, target_depth + 1):
            if board.turn == chess.WHITE:
                best_value = -math.inf
                for move in legal_moves:
                    board.push(move)
                    board_value = self.alpha_beta(board, current_depth - 1, -math.inf, math.inf, False)
                    board.pop()
                    if board_value > best_value:
                        best_value = board_value
                        best_move = move
            else:
                best_value = math.inf
                for move in legal_moves:
                    board.push(move)
                    board_value = self.alpha_beta(board, current_depth - 1, -math.inf, math.inf, True)
                    board.pop()
                    if board_value < best_value:
                        best_value = board_value
                        best_move = move
                        
        print(f"Skill Level: {self.skill_level} | Evaluated {self.nodes_visited} Positions | Best Move: {best_move}")
        return best_move

    # ==============================================================================
    # MANDATORY LICHESS-BOT WRAPPER FUNCTION
    # ==============================================================================
    def play(self, board, game=None):
        """Standardized interface entry point for lichess-bot framework"""
        return self.select_best_move(board)