from django.test import TestCase, Client
from django.urls import reverse
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.http import QueryDict

from accounts.models import FriendCode
from cards.models import Card
from trades.models import (
    TradeOffer,
    TradeOfferHaveCard,
    TradeOfferWantCard,
    TradeAcceptance,
)
from trades.forms import (
    TradeOfferCreateForm,
    TradeAcceptanceCreateForm,
    TradeOfferAcceptForm,
    TradeAcceptanceTransitionForm,
)
from tests.utils.rarity import RARITY_MAPPING

# ------------------------------------------------------------------------
# Model Tests
# ------------------------------------------------------------------------
class TradeOfferModelTest(TestCase):
    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(
            username="testuser", email="test@example.com", password="password"
        )
        self.friend_code = FriendCode.objects.create(
            friend_code="1234-5678-9012-3456", in_game_name="TestInGame", user=self.user
        )

        # Create cards with the same rarity (valid scenario)
        self.card1 = Card.objects.create(
            name="Card1", cardset="set1", cardnum=1, style="default", 
            rarity_icon=RARITY_MAPPING[1], rarity_level=1
        )
        self.card2 = Card.objects.create(
            name="Card2", cardset="set1", cardnum=2, style="default", 
            rarity_icon=RARITY_MAPPING[1], rarity_level=1
        )
        # Create a card with a different rarity (to test invalid trade offers)
        self.card3 = Card.objects.create(
            name="Card3", cardset="set1", cardnum=3, style="default", 
            rarity_icon=RARITY_MAPPING[8], rarity_level=8
        )

        # Create a valid trade offer with consistent rarity details
        self.trade_offer = TradeOffer.objects.create(initiated_by=self.friend_code)
        TradeOfferHaveCard.objects.create(
            trade_offer=self.trade_offer, card=self.card1, quantity=2
        )
        TradeOfferWantCard.objects.create(
            trade_offer=self.trade_offer, card=self.card2, quantity=3
        )

    def test_update_rarity_fields_valid(self):
        """Test update_rarity_fields succeeds with cards sharing the same rarity."""
        self.trade_offer.update_rarity_fields()
        self.assertEqual(self.trade_offer.rarity_level, 1)
        self.assertEqual(self.trade_offer.rarity_icon, "🔷")

    def test_update_rarity_fields_invalid(self):
        """If a card with a different rarity is added, update_rarity_fields should raise an error."""
        with self.assertRaisesMessage(
            ValidationError, "All cards in a trade offer must have the same rarity."
        ):
            TradeOfferHaveCard.objects.create(
                trade_offer=self.trade_offer, card=self.card3, quantity=1
            )

    def test_hash_generation(self):
        """Verify that TradeOffer.hash is generated and 9 characters long ending with 'z'."""
        self.assertTrue(self.trade_offer.hash.endswith("z"))
        self.assertEqual(len(self.trade_offer.hash), 9)


class TradeAcceptanceModelTest(TestCase):
    def setUp(self):
        User = get_user_model()
        # Create two users for testing state transitions
        self.user = User.objects.create_user(
            username="acceptuser", email="acc@example.com", password="password"
        )
        self.friend_code = FriendCode.objects.create(
            friend_code="1111-2222-3333-4444", in_game_name="AccInGame", user=self.user
        )
        self.other_user = User.objects.create_user(
            username="initiator", email="init@example.com", password="password"
        )
        self.initiator_friend_code = FriendCode.objects.create(
            friend_code="5555-6666-7777-8888", in_game_name="InitInGame", user=self.other_user
        )

        # Create two cards (with the same rarity)
        self.card1 = Card.objects.create(
            name="CardA", cardset="setA", cardnum=1, style="default", 
            rarity_icon=RARITY_MAPPING[2], rarity_level=2
        )
        self.card2 = Card.objects.create(
            name="CardB", cardset="setA", cardnum=2, style="default", 
            rarity_icon=RARITY_MAPPING[2], rarity_level=2
        )

        # Create a trade offer by the initiator.
        self.trade_offer = TradeOffer.objects.create(
            initiated_by=self.initiator_friend_code
        )
        TradeOfferHaveCard.objects.create(
            trade_offer=self.trade_offer, card=self.card1, quantity=1
        )
        TradeOfferWantCard.objects.create(
            trade_offer=self.trade_offer, card=self.card2, quantity=1
        )

        # Create an initial acceptance in state 'ACCEPTED'
        self.acceptance = TradeAcceptance.objects.create(
            trade_offer=self.trade_offer,
            accepted_by=self.friend_code,
            requested_card=self.card1,
            offered_card=self.card2,
            state=TradeAcceptance.AcceptanceState.ACCEPTED,
        )

    def test_invalid_state_transition(self):
        """
        Test that an invalid state transition (not allowed by the current state)
        raises a ValueError.
        """
        # Attempt to transition to a state that is not allowed.
        with self.assertRaises(ValueError):
            self.acceptance.update_state(
                TradeAcceptance.AcceptanceState.THANKED_BY_ACCEPTOR, user=self.user
            )

    def test_valid_state_transition_initiator(self):
        """
        For a user that owns the trade offer (initiator), check that a valid
        transition (e.g. from ACCEPTED to SENT) succeeds.
        """
        allowed_transitions = dict(
            self.acceptance.get_allowed_state_transitions(user=self.other_user)
        )
        # 'SENT' should be among allowed states for the initiator.
        self.assertIn(TradeAcceptance.AcceptanceState.SENT, allowed_transitions)

        self.acceptance.update_state(
            TradeAcceptance.AcceptanceState.SENT, user=self.other_user
        )
        self.assertEqual(
            self.acceptance.state, TradeAcceptance.AcceptanceState.SENT
        )

    def test_signal_adjusts_qty_accepted(self):
        """
        Test that creation, state change, and deletion of a TradeAcceptance
        update qty_accepted counters in through models correctly.
        """
        have_through = TradeOfferHaveCard.objects.get(
            trade_offer=self.trade_offer, card=self.card1
        )
        want_through = TradeOfferWantCard.objects.get(
            trade_offer=self.trade_offer, card=self.card2
        )
        # Initially, one active acceptance should set qty_accepted to 1.
        self.assertEqual(have_through.qty_accepted, 1)
        self.assertEqual(want_through.qty_accepted, 1)

        # Change state to a terminal state so that signals decrement the counter.
        self.acceptance.state = TradeAcceptance.AcceptanceState.REJECTED_BY_ACCEPTOR
        self.acceptance.save()
        have_through.refresh_from_db()
        want_through.refresh_from_db()
        self.assertEqual(have_through.qty_accepted, 0)
        self.assertEqual(want_through.qty_accepted, 0)

        # Create a new acceptance and then delete it.
        new_acceptance = TradeAcceptance.objects.create(
            trade_offer=self.trade_offer,
            accepted_by=self.friend_code,
            requested_card=self.card1,
            offered_card=self.card2,
            state=TradeAcceptance.AcceptanceState.ACCEPTED,
        )
        have_through.refresh_from_db()
        self.assertEqual(have_through.qty_accepted, 1)
        new_acceptance.delete()
        have_through.refresh_from_db()
        self.assertEqual(have_through.qty_accepted, 0)


# ------------------------------------------------------------------------
# Form Tests
# ------------------------------------------------------------------------
class TradeOfferFormTest(TestCase):
    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(
            username="formuser", email="form@example.com", password="password"
        )
        self.friend_code = FriendCode.objects.create(
            friend_code="9999-8888-7777-6666", in_game_name="FormUser", user=self.user
        )
        # Create two cards with the same rarity details.
        self.card1 = Card.objects.create(
            name="FormCard1", cardset="formset", cardnum=1, style="default",
            rarity_icon=RARITY_MAPPING[3], rarity_level=3
        )
        self.card2 = Card.objects.create(
            name="FormCard2", cardset="formset", cardnum=2, style="default",
            rarity_icon=RARITY_MAPPING[3], rarity_level=3
        )

    def test_trade_offer_create_form_valid(self):
        """
        A valid POST using colon-separated quantity strings should succeed.
        """
        # Build a QueryDict with multiple values for each list field.
        qd = QueryDict('', mutable=True)
        qd.setlist("have_cards", [f"{self.card1.pk}:2"])
        qd.setlist("want_cards", [f"{self.card2.pk}:3"])
        # 'initiated_by' is a normal field so we can update it directly.
        qd.update({"initiated_by": self.friend_code.pk})
        form = TradeOfferCreateForm(data=qd)
        self.assertTrue(form.is_valid())

    def test_trade_offer_create_form_invalid_quantity(self):
        """
        If quantity cannot be parsed as an integer a ValidationError should be raised.
        """
        qd = QueryDict('', mutable=True)
        # Provide an invalid quantity ("two" instead of an integer).
        qd.setlist("have_cards", [f"{self.card1.pk}:two"])
        qd.setlist("want_cards", [f"{self.card2.pk}:3"])
        qd.update({"initiated_by": self.friend_code.pk})
        form = TradeOfferCreateForm(data=qd)
        self.assertFalse(form.is_valid())
        self.assertIn("Invalid quantity provided", str(form.errors))

    def test_trade_offer_create_form_missing_colon(self):
        """
        An entry missing a colon should be ignored.
        """
        qd = QueryDict('', mutable=True)
        # No colon present in the selections.
        qd.setlist("have_cards", [f"{self.card1.pk}"])
        qd.setlist("want_cards", [f"{self.card2.pk}"])
        qd.update({"initiated_by": self.friend_code.pk})
        form = TradeOfferCreateForm(data=qd)
        self.assertTrue(form.is_valid())
        # Since the entries are ignored, cleaned_data should have empty dictionaries.
        self.assertEqual(form.cleaned_data["have_cards"], {})
        self.assertEqual(form.cleaned_data["want_cards"], {})

    def test_trade_acceptance_create_form(self):
        """Test that the TradeAcceptanceCreateForm filters available cards based on trade offer availability."""
        # Create a trade offer with available quantities.
        trade_offer = TradeOffer.objects.create(initiated_by=self.friend_code)
        TradeOfferHaveCard.objects.create(
            trade_offer=trade_offer, card=self.card1, quantity=2
        )
        TradeOfferWantCard.objects.create(
            trade_offer=trade_offer, card=self.card2, quantity=2
        )
        friend_codes = FriendCode.objects.filter(pk=self.friend_code.pk)
        form_data = {
            "accepted_by": self.friend_code.pk,
            "requested_card": self.card1.pk,
            "offered_card": self.card2.pk,
        }
        form = TradeAcceptanceCreateForm(
            data=form_data, trade_offer=trade_offer, friend_codes=friend_codes
        )
        self.assertTrue(form.is_valid())
        instance = form.save()
        self.assertEqual(instance.trade_offer, trade_offer)
        self.assertEqual(instance.accepted_by, self.friend_code)

    def test_trade_offer_accept_form(self):
        """Test that TradeOfferAcceptForm correctly sets the friend_code queryset."""
        friend_codes = FriendCode.objects.filter(pk=self.friend_code.pk)
        form = TradeOfferAcceptForm(friend_codes=friend_codes)
        self.assertEqual(
            list(form.fields["friend_code"].queryset), list(friend_codes)
        )

    def test_trade_acceptance_transition_form(self):
        """Test that the transition form provides only allowed transitions."""
        other_user = get_user_model().objects.create_user(
            username="transuser", email="trans@example.com", password="password"
        )
        other_friend_code = FriendCode.objects.create(
            friend_code="FC-TRANS", in_game_name="TransUser", user=other_user
        )
        # Create a trade offer with initiator being our test friend code.
        trade_offer = TradeOffer.objects.create(initiated_by=self.friend_code)
        TradeOfferHaveCard.objects.create(
            trade_offer=trade_offer, card=self.card1, quantity=1
        )
        TradeOfferWantCard.objects.create(
            trade_offer=trade_offer, card=self.card2, quantity=1
        )
        acceptance = TradeAcceptance.objects.create(
            trade_offer=trade_offer,
            accepted_by=other_friend_code,
            requested_card=self.card1,
            offered_card=self.card2,
            state=TradeAcceptance.AcceptanceState.ACCEPTED,
        )
        form = TradeAcceptanceTransitionForm(instance=acceptance, user=other_user)
        # Compare the form's state choices with the allowed transitions.
        allowed = [choice[0] for choice in acceptance.get_allowed_state_transitions(user=other_user)]
        form_choices = [choice[0] for choice in form.fields["state"].choices]
        for choice in allowed:
            self.assertIn(choice, form_choices)


# ------------------------------------------------------------------------
# View Tests
# ------------------------------------------------------------------------
class TradeViewsTest(TestCase):
    def setUp(self):
        User = get_user_model()
        self.client = Client()
        self.user = User.objects.create_user(
            username="viewuser", email="view@example.com", password="password"
        )
        self.friend_code = FriendCode.objects.create(
            friend_code="4444-3333-2222-1111", in_game_name="ViewUser", user=self.user
        )
        self.user.default_friend_code = self.friend_code
        self.user.save(update_fields=["default_friend_code"])
        self.client.login(username="viewuser", password="password")

        # Create sample cards.
        self.card1 = Card.objects.create(
            name="ViewCard1", cardset="setV", cardnum=1, style="default",
            rarity_icon=RARITY_MAPPING[7], rarity_level=7
        )
        self.card2 = Card.objects.create(
            name="ViewCard2", cardset="setV", cardnum=2, style="default",
            rarity_icon=RARITY_MAPPING[7], rarity_level=7
        )
        # Create a trade offer initiated by the logged-in user's friend code.
        self.trade_offer = TradeOffer.objects.create(initiated_by=self.friend_code)
        TradeOfferHaveCard.objects.create(
            trade_offer=self.trade_offer, card=self.card1, quantity=1
        )
        TradeOfferWantCard.objects.create(
            trade_offer=self.trade_offer, card=self.card2, quantity=1
        )

    def test_trade_offer_create_view_get(self):
        """GET request to TradeOfferCreateView should include friend_codes and cards in context."""
        response = self.client.get(reverse("trade_offer_create"))
        self.assertEqual(response.status_code, 200)
        self.assertIn("friend_codes", response.context)
        self.assertIn("cards", response.context)
        # When there is only one friend code, the initial value should be preset.
        self.assertEqual(
            response.context["form"].initial.get("initiated_by"),
            self.friend_code.pk,
        )

    def test_trade_offer_delete_view_permission(self):
        """
        The delete view should enforce that only trade offers initiated by one
        of the user's friend codes can be deleted.
        """
        other_user = get_user_model().objects.create_user(
            username="otheruser", email="other@example.com", password="password"
        )
        other_friend_code = FriendCode.objects.create(
            friend_code="FC-OTHER", in_game_name="OtherUser", user=other_user
        )
        trade_offer_other = TradeOffer.objects.create(initiated_by=other_friend_code)
        url = reverse("trade_offer_delete", kwargs={"pk": trade_offer_other.pk})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 403)

    def test_trade_offer_delete_view_close(self):
        """
        If a trade offer has active acceptances, the delete view should not delete it.
        Instead, if no active acceptances remain it should mark the offer as closed.
        """
        # Create a trade offer with an active acceptance.
        trade_offer_with_acceptance = TradeOffer.objects.create(initiated_by=self.friend_code)
        # Use quantity=2 so the trade offer isn't automatically closed when one acceptance is created
        TradeOfferHaveCard.objects.create(
            trade_offer=trade_offer_with_acceptance, card=self.card1, quantity=2
        )
        TradeOfferWantCard.objects.create(
            trade_offer=trade_offer_with_acceptance, card=self.card2, quantity=2
        )
        # Create an acceptance that takes one card from each side
        TradeAcceptance.objects.create(
            trade_offer=trade_offer_with_acceptance,
            accepted_by=self.friend_code,
            requested_card=self.card1,
            offered_card=self.card2,
            state=TradeAcceptance.AcceptanceState.ACCEPTED,
        )
        delete_url = reverse("trade_offer_delete", kwargs={"pk": trade_offer_with_acceptance.pk})

        # --- Patch the view's get_object() method to return our trade offer ---
        from trades.views import TradeOfferDeleteView
        orig_get_object = TradeOfferDeleteView.get_object
        TradeOfferDeleteView.get_object = lambda self: trade_offer_with_acceptance

        try:
            # First POST: with active acceptance, deletion should not close the offer.
            response = self.client.post(delete_url)
            trade_offer_with_acceptance.refresh_from_db()
            self.assertFalse(trade_offer_with_acceptance.is_closed)

            # Now simulate no active acceptances by updating the acceptance state.
            acceptance = trade_offer_with_acceptance.acceptances.first()
            acceptance.state = TradeAcceptance.AcceptanceState.REJECTED_BY_ACCEPTOR
            acceptance.save(update_fields=["state"])

            response = self.client.post(delete_url)
            trade_offer_with_acceptance.refresh_from_db()
            self.assertTrue(trade_offer_with_acceptance.is_closed)
        finally:
            # Always restore the original method.
            TradeOfferDeleteView.get_object = orig_get_object

    def test_trade_acceptance_update_view(self):
        """Test updating a trade acceptance via the update view."""
        trade_offer_new = TradeOffer.objects.create(initiated_by=self.friend_code)
        TradeOfferHaveCard.objects.create(
            trade_offer=trade_offer_new, card=self.card1, quantity=1
        )
        TradeOfferWantCard.objects.create(
            trade_offer=trade_offer_new, card=self.card2, quantity=1
        )
        acceptance = TradeAcceptance.objects.create(
            trade_offer=trade_offer_new,
            accepted_by=self.friend_code,
            requested_card=self.card1,
            offered_card=self.card2,
            state=TradeAcceptance.AcceptanceState.ACCEPTED,
        )
        update_url = reverse("trade_acceptance_update", kwargs={"pk": acceptance.pk})
        
        # First, try an invalid state update.
        response = self.client.post(update_url, {"state": "INVALID_STATE"})
        self.assertEqual(response.status_code, 200)
        
        form = response.context.get("form")
        self.assertIsNotNone(form, "Form should be present in the response context.")
        self.assertIn(
            "state", form.errors,
            "Expected an error on the 'state' field when an invalid state is submitted."
        )
        self.assertTrue(form.errors["state"], "The 'state' field should have error messages.")

        # Next, if there is an allowed valid transition, try it.
        allowed_states = [choice[0] for choice in acceptance.get_allowed_state_transitions(user=self.user)]
        if allowed_states:
            valid_state = allowed_states[0]
            response = self.client.post(update_url, {"state": valid_state})
            self.assertEqual(response.status_code, 302)


class TradeOfferSecurityTests(TestCase):
    def setUp(self):
        User = get_user_model()
        # Create three users for testing various security scenarios
        self.user1 = User.objects.create_user(
            username="user1", email="user1@example.com", password="password1"
        )
        self.user2 = User.objects.create_user(
            username="user2", email="user2@example.com", password="password2"
        )
        self.user3 = User.objects.create_user(
            username="user3", email="user3@example.com", password="password3"
        )

        # Create friend codes for each user with correct format
        self.fc1 = FriendCode.objects.create(
            friend_code="1111-2222-3333-4444", in_game_name="User1Game", user=self.user1
        )
        self.fc2 = FriendCode.objects.create(
            friend_code="5555-6666-7777-8888", in_game_name="User2Game", user=self.user2
        )
        self.fc3 = FriendCode.objects.create(
            friend_code="9999-0000-1111-2222", in_game_name="User3Game", user=self.user3
        )

        # Create test cards with proper rarity levels
        self.card1 = Card.objects.create(
            name="SecCard1", cardset="secset", cardnum=1, style="default",
            rarity_icon=RARITY_MAPPING[3], rarity_level=3
        )
        self.card2 = Card.objects.create(
            name="SecCard2", cardset="secset", cardnum=2, style="default",
            rarity_icon=RARITY_MAPPING[3], rarity_level=3
        )

        # Create a trade offer by user1
        self.trade_offer = TradeOffer.objects.create(initiated_by=self.fc1)
        TradeOfferHaveCard.objects.create(
            trade_offer=self.trade_offer, card=self.card1, quantity=1
        )
        TradeOfferWantCard.objects.create(
            trade_offer=self.trade_offer, card=self.card2, quantity=1
        )

        self.client = Client()

    def test_unauthorized_trade_offer_deletion(self):
        """Test that users cannot delete trade offers they don't own."""
        self.client.login(username="user2", password="password2")
        response = self.client.post(
            reverse("trade_offer_delete", kwargs={"pk": self.trade_offer.pk})
        )
        self.assertEqual(response.status_code, 403)
        self.assertTrue(TradeOffer.objects.filter(pk=self.trade_offer.pk).exists())

    def test_unauthorized_trade_acceptance_update(self):
        """Test that uninvolved users cannot update trade acceptances."""
        # Create an acceptance between user2 and user1's offer
        acceptance = TradeAcceptance.objects.create(
            trade_offer=self.trade_offer,
            accepted_by=self.fc2,
            requested_card=self.card1,
            offered_card=self.card2,
            state=TradeAcceptance.AcceptanceState.ACCEPTED,
        )

        # Try to update the acceptance as user3 (uninvolved)
        self.client.login(username="user3", password="password3")
        response = self.client.post(
            reverse("trade_acceptance_update", kwargs={"pk": acceptance.pk}),
            {"state": TradeAcceptance.AcceptanceState.SENT}
        )
        self.assertEqual(response.status_code, 403)

    def test_cross_user_friend_code_manipulation(self):
        """Test that users cannot use other users' friend codes."""
        self.client.login(username="user2", password="password2")
        
        # Try to create a trade offer using user1's friend code
        response = self.client.get(
            reverse("trade_offer_create"),
            {
                "initiated_by": self.fc1.pk,  # User1's friend code
                "have_cards": [f"{self.card1.pk}:1"],
                "want_cards": [f"{self.card2.pk}:1"],
            }
        )
        self.assertEqual(response.status_code, 200)  # Form should fail validation
        self.assertFalse(
            TradeOffer.objects.filter(initiated_by=self.fc1).count() > 1
        )

    def test_authenticated_only_views(self):
        """Test that authenticated-only views are properly protected."""
        # Test without login
        urls_to_test = [
            reverse("trade_offer_create"),
            reverse("trade_offer_dashboard"),
            reverse("trade_acceptance_create", kwargs={"offer_pk": self.trade_offer.pk}),
        ]
        
        # First ensure we're logged out
        self.client.logout()
        
        for url in urls_to_test:
            response = self.client.get(url)
            self.assertRedirects(
                response,
                f"/accounts/login/?next={url}",
                msg_prefix=f"URL {url} should require authentication"
            )


class TradeOfferEdgeCasesTest(TestCase):
    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(
            username="edgeuser", email="edge@example.com", password="password"
        )
        self.friend_code = FriendCode.objects.create(
            friend_code="3333-4444-5555-6666", in_game_name="EdgeUser", user=self.user
        )
        
        # Create test cards with different rarities using proper levels and icons
        self.common_card = Card.objects.create(
            name="CommonCard", cardset="edgeset", cardnum=1, style="default",
            rarity_icon=RARITY_MAPPING[1], rarity_level=1
        )
        self.rare_card = Card.objects.create(
            name="RareCard", cardset="edgeset", cardnum=2, style="default",
            rarity_icon=RARITY_MAPPING[5], rarity_level=5
        )
        self.crown_card = Card.objects.create(
            name="CrownCard", cardset="edgeset", cardnum=3, style="default",
            rarity_icon=RARITY_MAPPING[8], rarity_level=8
        )

        self.client = Client()
        self.client.login(username="edgeuser", password="password")

    def test_zero_quantity_trade_offer(self):
        """Test that trade offers with zero quantity are handled properly."""
        response = self.client.get(
            reverse("trade_offer_create"),
            {
                "initiated_by": self.friend_code.pk,
                "have_cards": [f"{self.common_card.pk}:0"],
                "want_cards": [f"{self.common_card.pk}:1"],
            }
        )
        self.assertEqual(response.status_code, 200)
        self.assertFalse(
            TradeOffer.objects.filter(initiated_by=self.friend_code).exists()
        )

    def test_negative_quantity_trade_offer(self):
        """Test that trade offers with negative quantity are handled properly."""
        response = self.client.get(
            reverse("trade_offer_create"),
            {
                "initiated_by": self.friend_code.pk,
                "have_cards": [f"{self.common_card.pk}:-1"],
                "want_cards": [f"{self.common_card.pk}:1"],
            }
        )
        self.assertEqual(response.status_code, 200)
        self.assertFalse(
            TradeOffer.objects.filter(initiated_by=self.friend_code).exists()
        )

    def test_mixed_rarity_trade_offer(self):
        """Test that trade offers with mixed rarity cards are rejected."""
        response = self.client.get(
            reverse("trade_offer_create"),
            {
                "initiated_by": self.friend_code.pk,
                "have_cards": [f"{self.common_card.pk}:1"],
                "want_cards": [f"{self.crown_card.pk}:1"],
            }
        )
        self.assertEqual(response.status_code, 200)
        self.assertFalse(
            TradeOffer.objects.filter(initiated_by=self.friend_code).exists()
        )

    def test_duplicate_card_entries(self):
        """Test handling of duplicate card entries in trade offers."""
        response = self.client.get(
            reverse("trade_offer_create"),
            {
                "initiated_by": self.friend_code.pk,
                "have_cards": [
                    f"{self.common_card.pk}:1",
                    f"{self.common_card.pk}:1"
                ],
                "want_cards": [f"{self.common_card.pk}:1"],
            }
        )
        self.assertEqual(response.status_code, 200)
        self.assertFalse(
            TradeOffer.objects.filter(initiated_by=self.friend_code).exists()
        )


class TradeSearchTests(TestCase):
    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(
            username="searchuser", email="search@example.com", password="password"
        )
        self.friend_code = FriendCode.objects.create(
            friend_code="7777-8888-9999-0000", in_game_name="SearchUser", user=self.user
        )

        # Create test cards with proper rarity levels
        self.card1 = Card.objects.create(
            name="SearchCard1", cardset="sc1", cardnum=1, style="default",
            rarity_icon=RARITY_MAPPING[4], rarity_level=4
        )
        self.card2 = Card.objects.create(
            name="SearchCard2", cardset="sc1", cardnum=2, style="default",
            rarity_icon=RARITY_MAPPING[4], rarity_level=4
        )
        self.card3 = Card.objects.create(
            name="SearchCard3", cardset="sc1", cardnum=3, style="default",
            rarity_icon=RARITY_MAPPING[4], rarity_level=4
        )

        # Create some trade offers
        self.trade_offer1 = TradeOffer.objects.create(initiated_by=self.friend_code)
        TradeOfferHaveCard.objects.create(
            trade_offer=self.trade_offer1, card=self.card1, quantity=2
        )
        TradeOfferWantCard.objects.create(
            trade_offer=self.trade_offer1, card=self.card2, quantity=1
        )

        self.trade_offer2 = TradeOffer.objects.create(initiated_by=self.friend_code)
        TradeOfferHaveCard.objects.create(
            trade_offer=self.trade_offer2, card=self.card2, quantity=1
        )
        TradeOfferWantCard.objects.create(
            trade_offer=self.trade_offer2, card=self.card3, quantity=1
        )

        self.client = Client()

    def test_search_by_have_cards(self):
        """Test searching for trade offers by cards the user has doesn't show offers initiated by the user."""
        response = self.client.post(
            reverse("trade_offer_search"),
            {
                "have_cards": [f"{self.card2.pk}:1"],
            }
        )
        self.assertEqual(response.status_code, 200)
        self.assertNotContains(response, self.trade_offer1.initiated_by.in_game_name)
        self.assertNotContains(response, self.trade_offer2.initiated_by.in_game_name)

    def test_search_by_want_cards(self):
        """Test searching for trade offers by cards the user wants doesn't show offers initiated by the user."""
        response = self.client.post(
            reverse("trade_offer_search"),
            {
                "want_cards": [f"{self.card1.pk}:1"],
            }
        )
        self.assertEqual(response.status_code, 200)
        self.assertNotContains(response, self.trade_offer1.initiated_by.in_game_name)
        self.assertNotContains(response, self.trade_offer2.initiated_by.in_game_name)

    def test_search_with_invalid_card_id(self):
        """Test search behavior with invalid card IDs."""
        response = self.client.post(
            reverse("trade_offer_search"),
            {
                "have_cards": ["999999:1"],  # Non-existent card ID
            }
        )
        self.assertEqual(response.status_code, 200)
        self.assertNotContains(response, self.trade_offer1.initiated_by.in_game_name)
        self.assertNotContains(response, self.trade_offer2.initiated_by.in_game_name)

    def test_search_closed_trades(self):
        """Test that closed trades don't appear in search results."""
        self.trade_offer1.is_closed = True
        self.trade_offer1.save()
        
        response = self.client.post(
            reverse("trade_offer_search"),
            {
                "have_cards": [f"{self.card2.pk}:1"],
            }
        )
        self.assertEqual(response.status_code, 200)
        self.assertNotContains(response, self.trade_offer1.initiated_by.in_game_name)


class TradeAcceptanceComplexTests(TestCase):
    def setUp(self):
        User = get_user_model()
        self.initiator = User.objects.create_user(
            username="initiator", email="init@example.com", password="password"
        )
        self.acceptor = User.objects.create_user(
            username="acceptor", email="accept@example.com", password="password"
        )
        
        self.initiator_fc = FriendCode.objects.create(
            friend_code="1234-5678-9012-3456", in_game_name="InitUser", user=self.initiator
        )
        self.acceptor_fc = FriendCode.objects.create(
            friend_code="6543-2109-8765-4321", in_game_name="AcceptUser", user=self.acceptor
        )

        # Create test cards with proper rarity levels
        self.card1 = Card.objects.create(
            name="ComplexCard1", cardset="cx1", cardnum=1, style="default",
            rarity_icon=RARITY_MAPPING[6], rarity_level=6
        )
        self.card2 = Card.objects.create(
            name="ComplexCard2", cardset="cx1", cardnum=2, style="default",
            rarity_icon=RARITY_MAPPING[6], rarity_level=6
        )
        self.card3 = Card.objects.create(
            name="ComplexCard3", cardset="cx1", cardnum=3, style="default",
            rarity_icon=RARITY_MAPPING[6], rarity_level=6
        )
        self.card4 = Card.objects.create(
            name="ComplexCard4", cardset="cx1", cardnum=4, style="default",
            rarity_icon=RARITY_MAPPING[6], rarity_level=6
        )

        # Create a trade offer with multiple quantities
        self.trade_offer = TradeOffer.objects.create(initiated_by=self.initiator_fc)
        TradeOfferHaveCard.objects.create(
            trade_offer=self.trade_offer, card=self.card1, quantity=3
        )
        TradeOfferHaveCard.objects.create(
            trade_offer=self.trade_offer, card=self.card3, quantity=1
        )
        TradeOfferWantCard.objects.create(
            trade_offer=self.trade_offer, card=self.card2, quantity=3
        )
        TradeOfferWantCard.objects.create(
            trade_offer=self.trade_offer, card=self.card4, quantity=1
        )

        self.client = Client()

    def test_multiple_acceptances_quantity_limit(self):
        """Test that multiple acceptances cannot exceed the offer's quantity limit."""
        self.client.login(username="acceptor", password="password")
        
        # Create first acceptance
        response1 = self.client.post(
            reverse("trade_acceptance_create", kwargs={"offer_pk": self.trade_offer.pk}),
            {
                "accepted_by": self.acceptor_fc.pk,
                "requested_card": self.card1.pk,
                "offered_card": self.card2.pk,
            }
        )
        self.assertEqual(response1.status_code, 302)  # Successful creation

        # Create second acceptance
        response2 = self.client.post(
            reverse("trade_acceptance_create", kwargs={"offer_pk": self.trade_offer.pk}),
            {
                "accepted_by": self.acceptor_fc.pk,
                "requested_card": self.card1.pk,
                "offered_card": self.card2.pk,
            }
        )
        self.assertEqual(response2.status_code, 302)  # Successful creation

        # Try to create a fourth acceptance (should fail as only 3 are allowed)
        response3 = self.client.post(
            reverse("trade_acceptance_create", kwargs={"offer_pk": self.trade_offer.pk}),
            {
                "accepted_by": self.acceptor_fc.pk,
                "requested_card": self.card1.pk,
                "offered_card": self.card2.pk,
            }
        )
        self.assertEqual(response3.status_code, 302)  # Successful creation

        response4 = self.client.post(
            reverse("trade_acceptance_create", kwargs={"offer_pk": self.trade_offer.pk}),
            {
                "accepted_by": self.acceptor_fc.pk,
                "requested_card": self.card1.pk,
                "offered_card": self.card2.pk,
            }
        )
        self.assertEqual(response4.status_code, 200)  # Should fail
        self.assertEqual(
            self.trade_offer.acceptances.count(), 3,
            "Should not allow more acceptances than the quantity limit"
        )

    def test_complex_state_transitions(self):
        """Test complex state transition scenarios."""
        self.client.login(username="acceptor", password="password")
        
        # Create an acceptance
        acceptance = TradeAcceptance.objects.create(
            trade_offer=self.trade_offer,
            accepted_by=self.acceptor_fc,
            requested_card=self.card1,
            offered_card=self.card2,
            state=TradeAcceptance.AcceptanceState.ACCEPTED,
        )

        # Test invalid state transition sequence
        invalid_transitions = [
            TradeAcceptance.AcceptanceState.THANKED_BY_ACCEPTOR,  # Can't thank before sending
            TradeAcceptance.AcceptanceState.RECEIVED,  # Can't receive before sending
            TradeAcceptance.AcceptanceState.THANKED_BY_BOTH,  # Can't thank by both directly
        ]

        for invalid_state in invalid_transitions:
            response = self.client.post(
                reverse("trade_acceptance_update", kwargs={"pk": acceptance.pk}),
                {"state": invalid_state}
            )
            self.assertEqual(response.status_code, 200)  # Should stay on form
            acceptance.refresh_from_db()
            self.assertEqual(
                acceptance.state,
                TradeAcceptance.AcceptanceState.ACCEPTED,
                f"Invalid transition to {invalid_state} should not be allowed"
            )

        # Test valid state transition sequence
        valid_transitions = [
            (self.initiator, TradeAcceptance.AcceptanceState.SENT),
            (self.acceptor, TradeAcceptance.AcceptanceState.RECEIVED),
            (self.initiator, TradeAcceptance.AcceptanceState.THANKED_BY_INITIATOR),
            (self.acceptor, TradeAcceptance.AcceptanceState.THANKED_BY_BOTH),
        ]

        for user, state in valid_transitions:
            self.client.login(username=user.username, password="password")
            response = self.client.post(
                reverse("trade_acceptance_update", kwargs={"pk": acceptance.pk}),
                {"state": state}
            )
            self.assertEqual(response.status_code, 302)  # Should redirect on success
            acceptance.refresh_from_db()
            self.assertEqual(
                acceptance.state,
                state,
                f"Valid transition to {state} should be allowed"
            )


