"""Tests for the 'All Games' tournament mode in the CLI."""

import unittest
from unittest.mock import MagicMock, patch
from click.testing import CliRunner

from agentduel_cli.main import cli
from agentduel_cli.agent_loader import get_agent_game


class TestAllGamesChallengeDisplay(unittest.TestCase):
    """Test that 'all' challenges display correctly."""

    def setUp(self):
        """Set up test fixtures."""
        self.runner = CliRunner()

    def test_list_shows_all_games_type(self):
        """Test that list command shows 'All Games' for all challenges."""
        mock_challenges = {
            "results": [
                {
                    "id": 1,
                    "agent_name": "MultiAgent",
                    "game_type": "all",
                    "created_at": "2024-01-01T00:00:00Z",
                    "expires_at": "2024-01-01T00:05:00Z",
                },
                {
                    "id": 2,
                    "agent_name": "SpecificAgent",
                    "game_type": "split-or-steal",
                    "created_at": "2024-01-01T00:00:00Z",
                    "expires_at": "2024-01-01T00:05:00Z",
                },
            ]
        }

        with patch("agentduel_cli.main.get_token", return_value="test_token"):
            with patch("agentduel_cli.api.APIClient.list_challenges", return_value=mock_challenges):
                result = self.runner.invoke(cli, ["list"])

        self.assertIn("All Games", result.output)
        self.assertIn("Split or Steal", result.output)

    def test_matches_shows_all_games_type(self):
        """Test that matches command shows 'All Games' for all matches."""
        mock_matches = {
            "results": [
                {
                    "id": 1,
                    "agent_b_name": "Opponent",
                    "game_type": "all",
                    "score_a": 2,
                    "score_b": 1,
                    "is_draw": False,
                    "created_at": "2024-01-01T00:00:00Z",
                },
            ]
        }

        with patch("agentduel_cli.main.get_token", return_value="test_token"):
            with patch("agentduel_cli.api.APIClient.get_my_matches", return_value=mock_matches):
                result = self.runner.invoke(cli, ["matches"])

        self.assertIn("All Games", result.output)


class TestAllGamesJoinValidation(unittest.TestCase):
    """Test validation rules for joining 'all' challenges."""

    def setUp(self):
        """Set up test fixtures."""
        self.runner = CliRunner()

    def test_agent_with_game_property_cannot_join_all(self):
        """Test that agent with GAME property cannot join 'all' challenges."""
        mock_challenges = {
            "results": [
                {
                    "id": 1,
                    "agent_name": "Opponent",
                    "game_type": "all",
                    "created_at": "2024-01-01T00:00:00Z",
                    "expires_at": "2024-01-01T00:05:00Z",
                },
            ]
        }

        with patch("agentduel_cli.main.get_token", return_value="test_token"):
            with patch("agentduel_cli.main.get_active_agent", return_value="/fake/agent.py"):
                with patch("os.path.exists", return_value=True):
                    with patch("agentduel_cli.main.load_agent") as mock_load:
                        # Mock an agent WITH a GAME property
                        with patch("agentduel_cli.main.get_agent_game", return_value="split-or-steal"):
                            with patch("agentduel_cli.api.APIClient.list_challenges", return_value=mock_challenges):
                                with patch("agentduel_cli.api.APIClient.get_my_agent", return_value={"name": "MyAgent"}):
                                    mock_load.return_value = MagicMock()
                                    result = self.runner.invoke(cli, ["join", "1"])

        # Should show error about needing multi-game agent
        self.assertIn("multi-game agent", result.output.lower())
        self.assertIn("GAME", result.output)

    def test_agent_without_game_property_can_join_all(self):
        """Test that agent without GAME property can join 'all' challenges."""
        mock_challenges = {
            "results": [
                {
                    "id": 1,
                    "agent_name": "Opponent",
                    "game_type": "all",
                    "created_at": "2024-01-01T00:00:00Z",
                    "expires_at": "2024-01-01T00:05:00Z",
                },
            ]
        }
        mock_join_result = {
            "match_id": 123,
            "agent_a": "Opponent",
            "agent_b": "MyAgent",
            "game_type": "all",
        }

        with patch("agentduel_cli.main.get_token", return_value="test_token"):
            with patch("agentduel_cli.main.get_active_agent", return_value="/fake/agent.py"):
                with patch("os.path.exists", return_value=True):
                    with patch("agentduel_cli.main.load_agent") as mock_load:
                        # Mock an agent WITHOUT a GAME property
                        with patch("agentduel_cli.main.get_agent_game", return_value=None):
                            with patch("agentduel_cli.api.APIClient.list_challenges", return_value=mock_challenges):
                                with patch("agentduel_cli.api.APIClient.get_my_agent", return_value={"name": "MyAgent"}):
                                    with patch("agentduel_cli.api.APIClient.join_challenge", return_value=mock_join_result):
                                        with patch("agentduel_cli.main.play_pvp_match") as mock_play:
                                            mock_load.return_value = MagicMock()
                                            result = self.runner.invoke(cli, ["join", "1"])

        # Should proceed to join
        self.assertIn("Joined challenge", result.output)
        self.assertIn("All Games", result.output)


class TestJoinAnyWithAllChallenges(unittest.TestCase):
    """Test --any flag behavior with 'all' challenges."""

    def setUp(self):
        """Set up test fixtures."""
        self.runner = CliRunner()

    def test_join_any_skips_all_challenges_for_specific_agent(self):
        """Test that --any skips 'all' challenges for agents with GAME property."""
        mock_challenges = {
            "results": [
                {
                    "id": 1,
                    "agent_name": "Opponent1",
                    "game_type": "all",  # This should be skipped
                    "created_at": "2024-01-01T00:00:00Z",
                    "expires_at": "2024-01-01T00:05:00Z",
                },
                {
                    "id": 2,
                    "agent_name": "Opponent2",
                    "game_type": "split-or-steal",  # This should be matched
                    "created_at": "2024-01-01T00:00:00Z",
                    "expires_at": "2024-01-01T00:05:00Z",
                },
            ]
        }
        mock_join_result = {
            "match_id": 123,
            "agent_a": "Opponent2",
            "agent_b": "MyAgent",
            "game_type": "split-or-steal",
        }

        with patch("agentduel_cli.main.get_token", return_value="test_token"):
            with patch("agentduel_cli.main.get_active_agent", return_value="/fake/agent.py"):
                with patch("os.path.exists", return_value=True):
                    with patch("agentduel_cli.main.load_agent") as mock_load:
                        # Mock agent with GAME = split-or-steal
                        with patch("agentduel_cli.main.get_agent_game", return_value="split-or-steal"):
                            with patch("agentduel_cli.api.APIClient.list_challenges", return_value=mock_challenges):
                                with patch("agentduel_cli.api.APIClient.get_my_agent", return_value={"name": "MyAgent"}):
                                    with patch("agentduel_cli.api.APIClient.join_challenge", return_value=mock_join_result) as mock_join:
                                        with patch("agentduel_cli.main.play_pvp_match"):
                                            mock_load.return_value = MagicMock()
                                            result = self.runner.invoke(cli, ["join", "--any"])

        # Should join the split-or-steal challenge, not the 'all' challenge
        mock_join.assert_called_once()
        call_args = mock_join.call_args
        self.assertEqual(call_args[0][0], 2)  # challenge_id should be 2

    def test_join_any_matches_all_challenges_for_multi_game_agent(self):
        """Test that --any matches 'all' challenges for agents without GAME property."""
        mock_challenges = {
            "results": [
                {
                    "id": 1,
                    "agent_name": "Opponent1",
                    "game_type": "all",  # This should be matched
                    "created_at": "2024-01-01T00:00:00Z",
                    "expires_at": "2024-01-01T00:05:00Z",
                },
            ]
        }
        mock_join_result = {
            "match_id": 123,
            "agent_a": "Opponent1",
            "agent_b": "MyAgent",
            "game_type": "all",
        }

        with patch("agentduel_cli.main.get_token", return_value="test_token"):
            with patch("agentduel_cli.main.get_active_agent", return_value="/fake/agent.py"):
                with patch("os.path.exists", return_value=True):
                    with patch("agentduel_cli.main.load_agent") as mock_load:
                        # Mock agent without GAME property
                        with patch("agentduel_cli.main.get_agent_game", return_value=None):
                            with patch("agentduel_cli.api.APIClient.list_challenges", return_value=mock_challenges):
                                with patch("agentduel_cli.api.APIClient.get_my_agent", return_value={"name": "MyAgent"}):
                                    with patch("agentduel_cli.api.APIClient.join_challenge", return_value=mock_join_result) as mock_join:
                                        with patch("agentduel_cli.main.play_pvp_match"):
                                            mock_load.return_value = MagicMock()
                                            result = self.runner.invoke(cli, ["join", "--any"])

        # Should join the 'all' challenge
        mock_join.assert_called_once()
        call_args = mock_join.call_args
        self.assertEqual(call_args[0][0], 1)  # challenge_id should be 1


class TestMultiGameAgentLoader(unittest.TestCase):
    """Test agent loading for multi-game agents."""

    def test_agent_without_game_property_returns_none(self):
        """Test that agent without GAME property returns None."""
        # Create a mock agent module without GAME
        mock_agent_class = type("Agent", (), {})
        mock_module = MagicMock()
        mock_module.Agent = mock_agent_class

        with patch("builtins.__import__", return_value=mock_module):
            with patch("importlib.util.spec_from_file_location") as mock_spec:
                with patch("importlib.util.module_from_spec") as mock_from_spec:
                    mock_spec.return_value = MagicMock()
                    mock_from_spec.return_value = mock_module
                    mock_spec.return_value.loader = MagicMock()

                    # Use a temp file approach
                    import tempfile
                    import os

                    with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
                        f.write("class Agent:\n    pass\n")
                        temp_path = f.name

                    try:
                        result = get_agent_game(temp_path)
                        # Since no GAME property, should return None
                        self.assertIsNone(result)
                    finally:
                        os.unlink(temp_path)

    def test_agent_with_game_property_returns_value(self):
        """Test that agent with GAME property returns the value."""
        import tempfile
        import os

        with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
            f.write("class Agent:\n    GAME = 'split-or-steal'\n")
            temp_path = f.name

        try:
            result = get_agent_game(temp_path)
            self.assertEqual(result, "split-or-steal")
        finally:
            os.unlink(temp_path)


class TestStatusWithMultiGameAgent(unittest.TestCase):
    """Test status command with multi-game agents."""

    def setUp(self):
        """Set up test fixtures."""
        self.runner = CliRunner()

    def test_status_shows_no_game_for_multi_game_agent(self):
        """Test that status shows 'Not specified' for agents without GAME."""
        mock_stats = {
            "name": "MultiAgent",
            "matches_played": 10,
            "wins": 5,
            "losses": 3,
            "draws": 2,
        }

        with patch("agentduel_cli.main.get_token", return_value="test_token"):
            with patch("agentduel_cli.main.get_active_agent", return_value="/fake/agent.py"):
                with patch("os.path.exists", return_value=True):
                    with patch("agentduel_cli.main.get_agent_game", return_value=None):
                        with patch("agentduel_cli.api.APIClient.get_me", return_value={"email": "test@test.com"}):
                            with patch("agentduel_cli.api.APIClient.get_agent_stats", return_value=mock_stats):
                                result = self.runner.invoke(cli, ["status"])

        self.assertIn("Not specified", result.output)


class TestExampleAgentsLoadable(unittest.TestCase):
    """Test that example multi-game agents can be loaded."""

    def test_multi_game_agent_loadable(self):
        """Test that multi_game_agent.py can be loaded."""
        import os
        import sys

        # Get path to example agent
        cli_dir = os.path.dirname(os.path.dirname(__file__))
        agent_path = os.path.join(cli_dir, "examples", "multi_game_agent.py")

        if os.path.exists(agent_path):
            from agentduel_cli.agent_loader import load_agent

            agent = load_agent(agent_path)

            # Should load successfully
            self.assertIsNotNone(agent)

            # Should not have GAME property
            self.assertFalse(hasattr(agent, "GAME"))

            # Should have required methods
            self.assertTrue(callable(getattr(agent, "on_game_start", None)))
            self.assertTrue(callable(getattr(agent, "on_turn", None)))
            self.assertTrue(callable(getattr(agent, "on_game_end", None)))

    def test_aggressive_multi_agent_loadable(self):
        """Test that aggressive_multi_agent.py can be loaded."""
        import os

        cli_dir = os.path.dirname(os.path.dirname(__file__))
        agent_path = os.path.join(cli_dir, "examples", "aggressive_multi_agent.py")

        if os.path.exists(agent_path):
            from agentduel_cli.agent_loader import load_agent

            agent = load_agent(agent_path)

            # Should load successfully
            self.assertIsNotNone(agent)

            # Should not have GAME property
            self.assertFalse(hasattr(agent, "GAME"))


class TestMultiGameAgentBehavior(unittest.TestCase):
    """Test multi-game agent behavior for different game types."""

    def setUp(self):
        """Load the multi-game agent for testing."""
        import os

        cli_dir = os.path.dirname(os.path.dirname(__file__))
        agent_path = os.path.join(cli_dir, "examples", "multi_game_agent.py")

        if os.path.exists(agent_path):
            from agentduel_cli.agent_loader import load_agent
            self.agent = load_agent(agent_path)
        else:
            self.agent = None

    def test_handles_split_or_steal_negotiate(self):
        """Test agent handles Split or Steal negotiate phase."""
        if not self.agent:
            self.skipTest("Multi-game agent not found")

        # Simulate Split or Steal game state
        self.agent.on_game_start({"game_number": 1, "game_id": "split-or-steal"})

        game_state = {
            "phase": "negotiate",
            "messages": [],
        }

        action = self.agent.on_turn(game_state)

        self.assertEqual(action["type"], "message")
        self.assertIsInstance(action["text"], str)

    def test_handles_split_or_steal_commit(self):
        """Test agent handles Split or Steal commit phase."""
        if not self.agent:
            self.skipTest("Multi-game agent not found")

        self.agent.on_game_start({"game_number": 1, "game_id": "split-or-steal"})

        game_state = {
            "phase": "commit",
            "messages": ["Hello", "Hi there"],
        }

        action = self.agent.on_turn(game_state)

        self.assertEqual(action["type"], "commit")
        self.assertIn(action["choice"], ["split", "steal"])

    def test_handles_liars_dice_opening_bid(self):
        """Test agent handles Liar's Dice opening bid."""
        if not self.agent:
            self.skipTest("Multi-game agent not found")

        self.agent.on_game_start({"game_number": 1, "game_id": "liars-dice"})

        game_state = {
            "phase": "bid",
            "your_dice": [2, 3, 3, 4, 1],
            "current_bid": None,
            "total_dice": 10,
        }

        action = self.agent.on_turn(game_state)

        self.assertEqual(action["action_type"], "bid")
        self.assertIsInstance(action["quantity"], int)
        self.assertIsInstance(action["face"], int)
        self.assertGreaterEqual(action["quantity"], 1)
        self.assertGreaterEqual(action["face"], 2)
        self.assertLessEqual(action["face"], 6)

    def test_handles_liars_dice_response(self):
        """Test agent handles Liar's Dice bid response."""
        if not self.agent:
            self.skipTest("Multi-game agent not found")

        self.agent.on_game_start({"game_number": 1, "game_id": "liars-dice"})

        game_state = {
            "phase": "bid",
            "your_dice": [2, 3, 3, 4, 1],
            "current_bid": {"quantity": 2, "face": 3},
            "total_dice": 10,
        }

        action = self.agent.on_turn(game_state)

        # Should either bid or challenge
        self.assertIn(action["action_type"], ["bid", "challenge"])

        if action["action_type"] == "bid":
            # Bid must be higher
            new_qty = action["quantity"]
            new_face = action["face"]
            self.assertTrue(
                new_qty > 2 or (new_qty == 2 and new_face > 3),
                "Bid must be higher than current bid"
            )


if __name__ == "__main__":
    unittest.main()
