import numpy as np
import pytest
from easydict import EasyDict

from connect4_env import Connect4Env
from zoo.board_games.connect4.envs.rule_bot import Connect4RuleBot


@pytest.mark.unittest
class TestConnect4RuleBot():
    """
    Overview:
        This class is used to test the Connect4RuleBot class methods.
    """

    def setup(self) -> None:
        """
        Overview:
            This method is responsible for setting up the initial configurations required for the game environment.
            It creates an instance of the Connect4Env class and Connect4RuleBot class.
        """
        cfg = EasyDict(
            battle_mode='self_play_mode',
            battle_mode_in_simulation_env='self_play_mode',
            render_mode='state_realtime_mode',
            channel_last=False,
            scale=True,
            agent_vs_human=False,
            prob_random_agent=0,
            prob_expert_agent=0,
            bot_action_type='rule',
            screen_scaling=9,
            save_replay=False,
            prob_random_action_in_bot=0
        )
        self.env = Connect4Env(cfg)
        self.player = 1
        self.bot = Connect4RuleBot(self.env, self.player)

    def test_is_winning_move(self) -> None:
        """
        Overview:
            This test method creates a game situation where the bot has three consecutive pieces in the board.
            It tests the `is_winning_move` method of the Connect4RuleBot class by asserting that the method returns True
            when a winning move is possible for the bot.
        """
        # Create a chessboard with three consecutive pieces.
        board = np.zeros((6, 7))
        board[5][3] = self.player
        board[5][4] = self.player
        board[5][5] = self.player
        self.bot.board = board
        assert self.bot.is_winning_move(2) is True  # Winning move is to place a piece in the second column.

    def test_is_winning_move_in_two_steps(self) -> None:
        board = np.zeros((6, 7))
        board[5][3] = self.player
        board[5][4] = self.player
        self.bot.board = board
        assert self.bot.is_winning_move_in_two_steps(2) is True
        board = np.zeros((6, 7))
        board[5][3] = self.player
        board[5][4] = self.player
        board[5][0] = 3 - self.player
        board[4][0] = 3 - self.player
        board[3][0] = 3 - self.player
        self.bot.board = board
        assert self.bot.is_winning_move_in_two_steps(2) is False

    def test_is_blocking_move(self) -> None:
        """
        Overview:
            This test method creates a game situation where the opponent has three consecutive pieces in the board.
            It tests the `is_blocking_move` method of the Connect4RuleBot class by asserting that the method returns True
            when a blocking move is necessary to prevent the opponent from winning.
        """
        """
        # Create a chessboard with three consecutive pieces.
        board = np.zeros((6, 7))
        opponent = 2 if self.player == 1 else 1
        board[5][3] = opponent
        board[5][4] = opponent
        board[5][5] = opponent
        self.bot.board = board
        assert self.bot.is_blocking_move(2) is True  # Placing a piece in the second column is a move to prevent the opponent from winning.
        """

        # Create a chessboard with three consecutive pieces of opponents.
        self.bot.current_player = 2
        board = np.array([[1, 0, 0, 0, 0, 0, 0],
                          [1, 0, 1, 0, 0, 0, 0],
                          [2, 0, 2, 0, 0, 0, 0],
                          [1, 1, 1, 0, 0, 0, 0],
                          [1, 2, 2, 1, 0, 0, 2],
                          [1, 2, 2, 2, 1, 0, 0]])
        self.bot.board = board
        assert self.bot.is_blocking_move(3) is True  # Placing a piece in the 4th column is a move to prevent the opponent from winning.

    def test_is_sequence_3_move(self) -> None:
        """
        Overview:
            This test method creates a game situation where the bot has two consecutive pieces in the board.
            It tests the `is_sequence_3_move` method of the Connect4RuleBot class by asserting that the method returns True
            when placing a piece next to these two consecutive pieces will create a sequence of 3 pieces.
        """
        # Create a chessboard with two consecutive pieces.
        board = np.zeros((6, 7))
        board[5][4] = self.player
        board[5][5] = self.player
        self.bot.board = board
        assert self.bot.is_sequence_3_move(3) is True  # Placing a piece in the third column should create a three-in-a-row.

    def test_is_sequence_2_move(self) -> None:
        """
        Overview:
            This test method creates a game situation where the bot has a single piece in the board.
            It tests the `is_sequence_2_move` method of the Connect4RuleBot class by asserting that the method returns True
            when placing a piece next to the single piece will create a sequence of 2 pieces.
            It also tests for situations where placing a piece will not result in a sequence of 2 pieces.
        """
        # Create a chessboard with one consecutive piece.
        board = np.zeros((6, 7))
        board[5][5] = self.player
        self.bot.board = board
        assert self.bot.is_sequence_2_move(4) is True  # Placing a move in the fourth column should create a two-in-a-row.

        # Create a chessboard with one and two consecutive pieces.
        board = np.array([[0, 0, 0, 0, 0, 0, 0],
                          [0, 0, 0, 0, 0, 0, 0],
                          [0, 0, 0, 0, 2, 0, 0],
                          [0, 0, 0, 0, 1, 0, 0],
                          [0, 0, 0, 0, 1, 0, 0],
                          [0, 0, 0, 2, 2, 0, 0]])
        self.bot.board = board
        assert self.bot.is_sequence_2_move(5) is True  # Placing a move in the 5th column should create a two-in-a-row.
        assert self.bot.is_sequence_2_move(4) is False  # Placing a move in the 5th column should not create a two-in-a-row.
        assert self.bot.is_sequence_2_move(6) is False  # Placing a move in the 6th column should not create a two-in-a-row.

    def test_get_action(self) -> None:
        """
        Overview:
            This test method creates a game situation with an empty board.
            It tests the `get_rule_bot_action` method of the Connect4RuleBot class by asserting that the method returns an action
            that is within the set of legal actions.
        """
        board = np.zeros((6, 7))
        self.bot.board = board
        action = self.bot.get_rule_bot_action(board, self.player)
        assert action in self.env.legal_actions

    def test_remove_actions(self) -> None:
        self.bot.next_player = 3 - self.player
        board = np.zeros((6, 7))
        board[5][0] = self.player
        board[5][3] = self.player
        board[5][4] = self.player
        board[5][5] = 3 - self.player
        board[4][3] = 3 - self.player
        board[4][4] = 3 - self.player
        board[4][5] = 3 - self.player
        self.bot.board = board
        self.bot.legal_actions = [i for i in range(7) if board[0][i] == 0]
        self.bot.remove_actions()
        assert self.bot.legal_actions == [0, 1, 3, 4, 5]
        board = np.zeros((6, 7))
        board[5][0] = self.player
        board[4][0] = self.player
        board[5][3] = 3 - self.player
        board[5][4] = 3 - self.player
        self.bot.board = board
        self.bot.legal_actions = [i for i in range(7) if board[0][i] == 0]
        self.bot.remove_actions()
        assert self.bot.legal_actions == [0, 2, 5]