"""
Test for the original Maya bug:

Maya took a 10 from discard and had to discard an Ace.

Bug chain:
1. should_take_discard() incorrectly decided to take the 10
2. choose_swap_or_discard() correctly returned None (don't swap)
3. But drawing from discard FORCES a swap
4. The forced-swap fallback found the "worst" visible card
5. The Ace (value 1) was swapped out for the 10

This test verifies the fixes work.
"""

import pytest
from game import Card, Player, Game, GameOptions, Suit, Rank
from ai import (
    GolfAI, CPUProfile, CPU_PROFILES,
    get_ai_card_value, has_worse_visible_card
)


def get_maya_profile() -> CPUProfile:
    """Get Maya's profile."""
    for p in CPU_PROFILES:
        if p.name == "Maya":
            return p
    # Fallback - create Maya-like profile
    return CPUProfile(
        name="Maya",
        style="Aggressive Closer",
        swap_threshold=6,
        pair_hope=0.4,
        aggression=0.85,
        unpredictability=0.1,
    )


def create_test_game() -> Game:
    """Create a game in playing state."""
    game = Game()
    game.add_player(Player(id="maya", name="Maya"))
    game.add_player(Player(id="other", name="Other"))
    game.start_game(options=GameOptions(initial_flips=0))
    return game


class TestMayaBugFix:
    """Test that the original Maya bug is fixed."""

    def test_maya_does_not_take_10_with_good_hand(self):
        """
        Original bug: Maya took a 10 from discard when she had good cards.

        Setup: Maya has visible Ace, King, 2 (all good cards)
        Discard: 10
        Expected: Maya should NOT take the 10
        """
        game = create_test_game()
        maya = game.get_player("maya")
        profile = get_maya_profile()

        # Set up Maya's hand with good visible cards
        maya.cards = [
            Card(Suit.HEARTS, Rank.ACE, face_up=True),   # Value 1
            Card(Suit.HEARTS, Rank.KING, face_up=True),  # Value 0
            Card(Suit.HEARTS, Rank.TWO, face_up=True),   # Value -2
            Card(Suit.SPADES, Rank.FIVE, face_up=False),
            Card(Suit.SPADES, Rank.SIX, face_up=False),
            Card(Suit.SPADES, Rank.SEVEN, face_up=False),
        ]

        # Put a 10 on discard
        discard_10 = Card(Suit.CLUBS, Rank.TEN, face_up=True)
        game.discard_pile = [discard_10]

        # Maya should NOT take the 10
        should_take = GolfAI.should_take_discard(discard_10, maya, profile, game)

        assert should_take is False, (
            "Maya should not take a 10 when her visible cards are Ace, King, 2"
        )

    def test_maya_does_not_take_10_even_with_unpredictability(self):
        """
        The unpredictability trait should NOT cause taking bad cards.

        Run multiple times to account for randomness.
        """
        game = create_test_game()
        maya = game.get_player("maya")
        profile = get_maya_profile()

        maya.cards = [
            Card(Suit.HEARTS, Rank.ACE, face_up=True),
            Card(Suit.HEARTS, Rank.KING, face_up=True),
            Card(Suit.HEARTS, Rank.TWO, face_up=True),
            Card(Suit.SPADES, Rank.FIVE, face_up=False),
            Card(Suit.SPADES, Rank.SIX, face_up=False),
            Card(Suit.SPADES, Rank.SEVEN, face_up=False),
        ]

        discard_10 = Card(Suit.CLUBS, Rank.TEN, face_up=True)
        game.discard_pile = [discard_10]

        # Run 100 times - should NEVER take the 10
        took_10_count = 0
        for _ in range(100):
            if GolfAI.should_take_discard(discard_10, maya, profile, game):
                took_10_count += 1

        assert took_10_count == 0, (
            f"Maya took a 10 {took_10_count}/100 times despite having good cards. "
            "Unpredictability should not override basic logic for bad cards."
        )

    def test_has_worse_visible_card_utility(self):
        """Test the utility function that guards against taking bad cards."""
        game = create_test_game()
        maya = game.get_player("maya")
        options = game.options

        # Hand with good visible cards (Ace=1, King=0, 2=-2)
        maya.cards = [
            Card(Suit.HEARTS, Rank.ACE, face_up=True),   # 1
            Card(Suit.HEARTS, Rank.KING, face_up=True),  # 0
            Card(Suit.HEARTS, Rank.TWO, face_up=True),   # -2
            Card(Suit.SPADES, Rank.FIVE, face_up=False),
            Card(Suit.SPADES, Rank.SIX, face_up=False),
            Card(Suit.SPADES, Rank.SEVEN, face_up=False),
        ]

        # No visible card is worse than 10 (value 10)
        assert has_worse_visible_card(maya, 10, options) is False

        # No visible card is worse than 5
        assert has_worse_visible_card(maya, 5, options) is False

        # Ace (1) is worse than 0
        assert has_worse_visible_card(maya, 0, options) is True

    def test_forced_swap_uses_house_rules(self):
        """
        When forced to swap (drew from discard), the AI should use
        get_ai_card_value() to find the worst card, not raw value().

        This matters for house rules like super_kings, ten_penny, etc.
        """
        game = create_test_game()
        game.options = GameOptions(super_kings=True)  # Kings now worth -2
        maya = game.get_player("maya")

        # All face up - forced swap scenario
        maya.cards = [
            Card(Suit.HEARTS, Rank.KING, face_up=True),   # -2 with super_kings
            Card(Suit.HEARTS, Rank.ACE, face_up=True),    # 1
            Card(Suit.HEARTS, Rank.THREE, face_up=True),  # 3 - worst!
            Card(Suit.SPADES, Rank.KING, face_up=True),   # -2 with super_kings
            Card(Suit.SPADES, Rank.TWO, face_up=True),    # -2
            Card(Suit.SPADES, Rank.ACE, face_up=True),    # 1
        ]

        # Find worst card using house rules
        worst_pos = 0
        worst_val = -999
        for i, c in enumerate(maya.cards):
            card_val = get_ai_card_value(c, game.options)
            if card_val > worst_val:
                worst_val = card_val
                worst_pos = i

        # Position 2 (Three, value 3) should be worst
        assert worst_pos == 2, (
            f"With super_kings, the Three (value 3) should be worst, "
            f"not position {worst_pos} (value {worst_val})"
        )

    def test_choose_swap_does_not_discard_excellent_cards(self):
        """
        Unpredictability should NOT cause discarding excellent cards (2s, Jokers).
        """
        game = create_test_game()
        maya = game.get_player("maya")
        profile = get_maya_profile()

        maya.cards = [
            Card(Suit.HEARTS, Rank.FIVE, face_up=True),
            Card(Suit.HEARTS, Rank.SIX, face_up=True),
            Card(Suit.HEARTS, Rank.SEVEN, face_up=False),
            Card(Suit.SPADES, Rank.EIGHT, face_up=False),
            Card(Suit.SPADES, Rank.NINE, face_up=False),
            Card(Suit.SPADES, Rank.TEN, face_up=False),
        ]

        # Drew a 2 (excellent card, value -2)
        drawn_two = Card(Suit.CLUBS, Rank.TWO)

        # Run 100 times - should ALWAYS swap (never discard a 2)
        discarded_count = 0
        for _ in range(100):
            swap_pos = GolfAI.choose_swap_or_discard(drawn_two, maya, profile, game)
            if swap_pos is None:
                discarded_count += 1

        assert discarded_count == 0, (
            f"Maya discarded a 2 (excellent card) {discarded_count}/100 times. "
            "Unpredictability should not cause discarding excellent cards."
        )

    def test_full_scenario_maya_10_ace(self):
        """
        Full reproduction of the original bug scenario.

        Maya has: [A, K, 2, ?, ?, ?] (good visible cards)
        Discard: 10

        Expected behavior:
        1. Maya should NOT take the 10
        2. If she somehow did, she should swap into face-down, not replace the Ace
        """
        game = create_test_game()
        maya = game.get_player("maya")
        profile = get_maya_profile()

        # Setup exactly like the bug report
        maya.cards = [
            Card(Suit.HEARTS, Rank.ACE, face_up=True),   # Good - don't replace!
            Card(Suit.HEARTS, Rank.KING, face_up=True),  # Good
            Card(Suit.HEARTS, Rank.TWO, face_up=True),   # Excellent
            Card(Suit.SPADES, Rank.JACK, face_up=False), # Unknown
            Card(Suit.SPADES, Rank.QUEEN, face_up=False),# Unknown
            Card(Suit.SPADES, Rank.TEN, face_up=False),  # Unknown
        ]

        discard_10 = Card(Suit.CLUBS, Rank.TEN, face_up=True)
        game.discard_pile = [discard_10]

        # Step 1: Maya should not take the 10
        should_take = GolfAI.should_take_discard(discard_10, maya, profile, game)
        assert should_take is False, "Maya should not take a 10 with this hand"

        # Step 2: Even if she did take it (simulating old bug), verify swap logic
        # The swap logic should prefer face-down positions
        drawn_10 = Card(Suit.CLUBS, Rank.TEN)
        swap_pos = GolfAI.choose_swap_or_discard(drawn_10, maya, profile, game)

        # Should either discard (None) or swap into face-down (positions 3, 4, 5)
        # Should NEVER swap into position 0 (Ace), 1 (King), or 2 (Two)
        if swap_pos is not None:
            assert swap_pos >= 3, (
                f"Maya tried to swap 10 into position {swap_pos}, replacing a good card. "
                "Should only swap into face-down positions (3, 4, 5)."
            )


class TestEdgeCases:
    """Test edge cases related to the bug."""

    def test_all_face_up_forced_swap_finds_actual_worst(self):
        """
        When all cards are face up and forced to swap, find the ACTUAL worst card.
        """
        game = create_test_game()
        maya = game.get_player("maya")

        # All face up, varying values
        maya.cards = [
            Card(Suit.HEARTS, Rank.ACE, face_up=True),    # 1
            Card(Suit.HEARTS, Rank.KING, face_up=True),   # 0
            Card(Suit.HEARTS, Rank.TWO, face_up=True),    # -2
            Card(Suit.SPADES, Rank.JACK, face_up=True),   # 10 - WORST
            Card(Suit.SPADES, Rank.THREE, face_up=True),  # 3
            Card(Suit.SPADES, Rank.FOUR, face_up=True),   # 4
        ]

        # Find worst
        worst_pos = 0
        worst_val = -999
        for i, c in enumerate(maya.cards):
            card_val = get_ai_card_value(c, game.options)
            if card_val > worst_val:
                worst_val = card_val
                worst_pos = i

        assert worst_pos == 3, f"Jack (position 3, value 10) should be worst, got position {worst_pos}"
        assert worst_val == 10, f"Worst value should be 10, got {worst_val}"

    def test_take_discard_respects_pair_potential(self):
        """
        Taking a bad card to complete a pair IS valid strategy.
        This should still work after the bug fix.
        """
        game = create_test_game()
        maya = game.get_player("maya")
        profile = get_maya_profile()

        # Maya has a visible 10 - taking another 10 to pair is GOOD
        maya.cards = [
            Card(Suit.HEARTS, Rank.TEN, face_up=True),   # Visible 10
            Card(Suit.HEARTS, Rank.KING, face_up=True),
            Card(Suit.HEARTS, Rank.ACE, face_up=True),
            Card(Suit.SPADES, Rank.FIVE, face_up=False), # Pair position for the 10
            Card(Suit.SPADES, Rank.SIX, face_up=False),
            Card(Suit.SPADES, Rank.SEVEN, face_up=False),
        ]

        # 10 on discard - should take to pair!
        discard_10 = Card(Suit.CLUBS, Rank.TEN, face_up=True)
        game.discard_pile = [discard_10]

        should_take = GolfAI.should_take_discard(discard_10, maya, profile, game)
        assert should_take is True, (
            "Maya SHOULD take a 10 when she has a visible 10 to pair with"
        )


class TestAvoidBadPairs:
    """Test that AI avoids creating wasteful pairs with negative cards."""

    def test_filter_bad_pair_positions_with_visible_two(self):
        """
        When placing a 2, avoid positions where column partner is a visible 2.

        Setup: Visible 2 at position 0
        Placing: Another 2
        Expected: Position 3 should be filtered out (would pair with position 0)
        """
        from ai import filter_bad_pair_positions

        game = create_test_game()
        player = game.get_player("maya")

        # Position 0 has a visible 2
        player.cards = [
            Card(Suit.HEARTS, Rank.TWO, face_up=True),   # Pos 0: visible 2
            Card(Suit.HEARTS, Rank.FIVE, face_up=True),  # Pos 1
            Card(Suit.HEARTS, Rank.SIX, face_up=True),   # Pos 2
            Card(Suit.SPADES, Rank.SEVEN, face_up=False), # Pos 3: column partner of 0
            Card(Suit.SPADES, Rank.EIGHT, face_up=False), # Pos 4
            Card(Suit.SPADES, Rank.NINE, face_up=False),  # Pos 5
        ]

        drawn_two = Card(Suit.CLUBS, Rank.TWO)
        face_down = [3, 4, 5]

        safe_positions = filter_bad_pair_positions(face_down, drawn_two, player, game.options)

        # Position 3 should be filtered out (would pair with visible 2 at position 0)
        assert 3 not in safe_positions, (
            "Position 3 should be filtered - would create wasteful 2-2 pair"
        )
        assert 4 in safe_positions
        assert 5 in safe_positions

    def test_filter_allows_positive_card_pairs(self):
        """
        Positive value cards can be paired - no filtering needed.

        Pairing a 5 with another 5 is GOOD (saves 10 points).
        """
        from ai import filter_bad_pair_positions

        game = create_test_game()
        player = game.get_player("maya")

        # Position 0 has a visible 5
        player.cards = [
            Card(Suit.HEARTS, Rank.FIVE, face_up=True),   # Pos 0: visible 5
            Card(Suit.HEARTS, Rank.SIX, face_up=True),
            Card(Suit.HEARTS, Rank.SEVEN, face_up=True),
            Card(Suit.SPADES, Rank.EIGHT, face_up=False), # Pos 3: column partner
            Card(Suit.SPADES, Rank.NINE, face_up=False),
            Card(Suit.SPADES, Rank.TEN, face_up=False),
        ]

        drawn_five = Card(Suit.CLUBS, Rank.FIVE)
        face_down = [3, 4, 5]

        safe_positions = filter_bad_pair_positions(face_down, drawn_five, player, game.options)

        # All positions should be allowed - pairing 5s is good!
        assert safe_positions == face_down

    def test_choose_swap_avoids_pairing_twos(self):
        """
        The full choose_swap_or_discard flow should avoid placing 2s
        in positions that would pair them.

        Run multiple times to verify randomness doesn't cause bad pairs.
        """
        game = create_test_game()
        maya = game.get_player("maya")
        profile = get_maya_profile()

        # Position 0 has a visible 2
        maya.cards = [
            Card(Suit.HEARTS, Rank.TWO, face_up=True),    # Pos 0: visible 2
            Card(Suit.HEARTS, Rank.FIVE, face_up=True),   # Pos 1
            Card(Suit.HEARTS, Rank.SIX, face_up=True),    # Pos 2
            Card(Suit.SPADES, Rank.SEVEN, face_up=False), # Pos 3: BAD - would pair
            Card(Suit.SPADES, Rank.EIGHT, face_up=False), # Pos 4: OK
            Card(Suit.SPADES, Rank.NINE, face_up=False),  # Pos 5: OK
        ]

        drawn_two = Card(Suit.CLUBS, Rank.TWO)

        # Run 100 times - should NEVER pick position 3
        bad_pair_count = 0
        for _ in range(100):
            swap_pos = GolfAI.choose_swap_or_discard(drawn_two, maya, profile, game)
            if swap_pos == 3:
                bad_pair_count += 1

        assert bad_pair_count == 0, (
            f"AI picked position 3 (creating 2-2 pair) {bad_pair_count}/100 times. "
            "Should avoid positions that waste negative card value."
        )

    def test_forced_swap_avoids_pairing_twos(self):
        """
        Even when forced to swap from discard, AI should avoid bad pairs.
        """
        from ai import filter_bad_pair_positions

        game = create_test_game()
        player = game.get_player("maya")

        # Position 1 has a visible 2, only positions 3, 4 are face-down
        player.cards = [
            Card(Suit.HEARTS, Rank.FIVE, face_up=True),
            Card(Suit.HEARTS, Rank.TWO, face_up=True),    # Pos 1: visible 2
            Card(Suit.HEARTS, Rank.SIX, face_up=True),
            Card(Suit.SPADES, Rank.SEVEN, face_up=True),
            Card(Suit.SPADES, Rank.EIGHT, face_up=False), # Pos 4: BAD - pairs with pos 1
            Card(Suit.SPADES, Rank.NINE, face_up=False),  # Pos 5: OK
        ]

        drawn_two = Card(Suit.CLUBS, Rank.TWO)
        face_down = [4, 5]

        safe_positions = filter_bad_pair_positions(face_down, drawn_two, player, game.options)

        # Position 4 should be filtered out (would pair with visible 2 at position 1)
        assert 4 not in safe_positions
        assert 5 in safe_positions

    def test_all_positions_bad_falls_back(self):
        """
        If ALL positions would create bad pairs, fall back to original list.
        (Must place the card somewhere)
        """
        from ai import filter_bad_pair_positions

        game = create_test_game()
        player = game.get_player("maya")

        # Only position 3 is face-down, and it would pair with visible 2 at position 0
        player.cards = [
            Card(Suit.HEARTS, Rank.TWO, face_up=True),    # Pos 0: visible 2
            Card(Suit.HEARTS, Rank.FIVE, face_up=True),
            Card(Suit.HEARTS, Rank.SIX, face_up=True),
            Card(Suit.SPADES, Rank.SEVEN, face_up=False), # Pos 3: only option, but bad
            Card(Suit.SPADES, Rank.EIGHT, face_up=True),
            Card(Suit.SPADES, Rank.NINE, face_up=True),
        ]

        drawn_two = Card(Suit.CLUBS, Rank.TWO)
        face_down = [3]  # Only option

        safe_positions = filter_bad_pair_positions(face_down, drawn_two, player, game.options)

        # Should return original list since there's no alternative
        assert safe_positions == face_down


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
