from django.test import TestCase, Client
from django.template import Template, Context
from datetime import timedelta

from django.urls import reverse
from django.utils import timezone

from accounts.models import CustomUser, FriendCode
from cards.models import Card, Deck, DeckNameTranslation, CardNameTranslation
from trades.models import TradeOffer, TradeOfferHaveCard, TradeOfferWantCard
from cards.templatetags import card_badge, card_multiselect
from tests.utils.rarity import RARITY_MAPPING

class CardsModelsTests(TestCase):
    def setUp(self):
        self.deck = Deck.objects.create(
            name="Test Deck", hex_color="#FFFFFF", cardset="A"
        )
        self.card = Card.objects.create(
            name="Test Card",
            cardset="A",
            cardnum=1,
            style="default",
            rarity_icon=RARITY_MAPPING[1],
            rarity_level=1
        )
        # Establish many-to-many relationship.
        self.card.decks.add(self.deck)

    def test_card_str(self):
        expected = f"{self.card.name} ({self.card.cardset} #{self.card.cardnum})"
        self.assertEqual(str(self.card), expected)

    def test_deck_str(self):
        self.assertEqual(str(self.deck), self.deck.name)

    def test_deck_name_translation_str(self):
        deck_translation = DeckNameTranslation.objects.create(
            name="Deck Translated", deck=self.deck, language="en"
        )
        self.assertEqual(str(deck_translation), "Deck Translated")

    def test_card_name_translation_str(self):
        card_translation = CardNameTranslation.objects.create(
            name="Card Translated", card=self.card, language="en"
        )
        self.assertEqual(str(card_translation), "Card Translated") 

class CardTemplatetagsTests(TestCase):
    def setUp(self):
        # Create a dummy card to use in template tag tests.
        self.card = Card.objects.create(
            name="Template Test Card",
            cardset="B",
            cardnum=2,
            style="background: green;",
            rarity_icon="☆",
            rarity_level=2
        )

    def test_card_badge_inclusion_tag(self):
        """Test the card_badge inclusion tag renders correctly."""
        template_str = '{% load card_badge %}{% card_badge card quantity=3 %}'
        t = Template(template_str)
        c = Context({"card": self.card})
        rendered = t.render(c)
        # Check that the rendered HTML contains the card name, quantity, and rarity.
        self.assertIn(self.card.name, rendered)
        self.assertIn("3", rendered)
        self.assertIn(self.card.rarity_icon, rendered)

    def test_card_badge_inline_filter(self):
        """Test the card_badge_inline filter returns safe HTML with correct data."""
        template_str = '{% load card_badge %}{{ card|card_badge_inline:5 }}'
        t = Template(template_str)
        c = Context({"card": self.card})
        rendered = t.render(c)
        self.assertIn(self.card.name, rendered)
        self.assertIn("5", rendered)
        self.assertIn(self.card.rarity_icon, rendered)

    def test_card_multiselect_tag_no_selected_values(self):
        """Test card_multiselect tag with no selected values."""
        context = card_multiselect.card_multiselect(
            field_name="cards",
            label="Select Cards",
            placeholder="Choose a card",
            cards=[self.card],
            selected_values=None,
        )
        self.assertEqual(context["field_name"], "cards")
        self.assertEqual(context["label"], "Select Cards")
        self.assertEqual(context["placeholder"], "Choose a card")
        # When no cards are preselected, each card should have default attributes.
        for card in context["cards"]:
            self.assertFalse(getattr(card, "selected", False))
            self.assertEqual(getattr(card, "selected_quantity", 1), 1)
        self.assertEqual(context["selected_values"], [])

    def test_card_multiselect_tag_with_selected_values(self):
        """Test card_multiselect tag with preselected values (testing both with and without explicit quantity)."""
        # Create a second card.
        card2 = Card.objects.create(
            name="Another Card",
            cardset="B",
            cardnum=3,
            style="background: blue;",
            rarity_icon="★",
            rarity_level=2,
        )
        selected_values = [f"{self.card.pk}:4", f"{card2.pk}"]
        context = card_multiselect.card_multiselect(
            field_name="cards",
            label="Select Cards",
            placeholder="Choose a card",
            cards=[self.card, card2],
            selected_values=selected_values,
        )
        # Verify that self.card is marked as selected with quantity "4" and card2 with default quantity 1.
        for card in context["cards"]:
            if card.pk == self.card.pk:
                self.assertTrue(getattr(card, "selected", False))
                self.assertEqual(getattr(card, "selected_quantity", 1), "4")
            elif card.pk == card2.pk:
                self.assertTrue(getattr(card, "selected", False))
                self.assertEqual(getattr(card, "selected_quantity", 1), 1)
            else:
                self.fail("Unexpected card in the multiselect context.")
        self.assertCountEqual(
            context["selected_values"], [str(self.card.pk), str(card2.pk)]
        )

    def test_card_multiselect_default_cards_when_none_provided(self):
        """Test that card_multiselect defaults to Card.objects.all() when no cards are provided."""
        # Capture all cards from the database.
        default_cards = list(Card.objects.all())
        context = card_multiselect.card_multiselect(
            field_name="cards",
            label="Select Cards",
            placeholder="Choose a card",
            cards=None,
            selected_values=[],
        )
        # Verify that the context's cards match those in the database.
        self.assertEqual(list(context["cards"]), default_cards) 

class CardsViewsTests(TestCase):
    def setUp(self):
        self.client = Client()
        # Create a test user and friend code for trade offers.
        self.user = CustomUser.objects.create_user(
            username="testuser", password="secret", email="test@example.com"
        )
        self.friendcode = FriendCode.objects.create(
            user=self.user, friend_code="1234-5678-9012", in_game_name="TestPlayer"
        )
        # Create a test card.
        self.card = Card.objects.create(
            name="Test Card",
            cardset="A",
            cardnum=1,
            style="default",
            rarity_icon=RARITY_MAPPING[1],
            rarity_level=1
        )

    def test_card_detail_view_context(self):
        """Test that the card detail view includes correct trade offer counts in context."""
        # Create a trade offer where the card appears as a "have" card.
        trade_offer_have = TradeOffer.objects.create(initiated_by=self.friendcode)
        TradeOfferHaveCard.objects.create(
            trade_offer=trade_offer_have, card=self.card, quantity=2
        )

        # Create a trade offer where the card appears as a "want" card.
        trade_offer_want = TradeOffer.objects.create(initiated_by=self.friendcode)
        TradeOfferWantCard.objects.create(
            trade_offer=trade_offer_want, card=self.card, quantity=3
        )

        url = reverse("cards:card_detail", kwargs={"pk": self.card.pk})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        # Verify that the card instance is in context.
        self.assertEqual(response.context["card"], self.card)
        # Verify that the counts are correctly computed.
        self.assertEqual(response.context.get("trade_offer_have_count"), 1)
        self.assertEqual(response.context.get("trade_offer_want_count"), 1)

    def test_card_detail_view_404(self):
        """Test that the card detail view returns a 404 for a non-existent card."""
        url = reverse("cards:card_detail", kwargs={"pk": 99999})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

    def create_trade_offer_for_have(self, updated_delta_minutes=0):
        """
        Helper method to create a trade offer for the 'have' side with a custom updated_at.
        """
        offer = TradeOffer.objects.create(initiated_by=self.friendcode)
        TradeOfferHaveCard.objects.create(
            trade_offer=offer, card=self.card, quantity=1
        )
        # Adjust updated_at so that ordering can be tested.
        new_time = timezone.now() + timedelta(minutes=updated_delta_minutes)
        TradeOffer.objects.filter(pk=offer.pk).update(updated_at=new_time)
        offer.refresh_from_db()
        return offer

    def create_trade_offer_for_want(self, updated_delta_minutes=0):
        """
        Helper method to create a trade offer for the 'want' side with a custom updated_at.
        """
        offer = TradeOffer.objects.create(initiated_by=self.friendcode)
        TradeOfferWantCard.objects.create(
            trade_offer=offer, card=self.card, quantity=1
        )
        new_time = timezone.now() + timedelta(minutes=updated_delta_minutes)
        TradeOffer.objects.filter(pk=offer.pk).update(updated_at=new_time)
        offer.refresh_from_db()
        return offer

    def test_trade_offer_have_list_view_pagination_and_ordering(self):
        """Test the have list view for correct pagination and ordering."""
        # Create three trade offers with distinct updated_at times.
        offer1 = self.create_trade_offer_for_have(updated_delta_minutes=1)
        offer2 = self.create_trade_offer_for_have(updated_delta_minutes=2)
        offer3 = self.create_trade_offer_for_have(updated_delta_minutes=3)

        url = reverse("cards:card_trade_offer_have_list", kwargs={"pk": self.card.pk})

        # Test default ordering ("newest" which orders descending by updated_at).
        response = self.client.get(url, {"order": "newest"})
        self.assertEqual(response.status_code, 200)
        trade_offers = response.context.get("trade_offers")
        self.assertEqual(response.context.get("side"), "have")
        # With paginate_by=2, the first page should have 2 offers.
        self.assertEqual(len(trade_offers), 2)
        # The first offer should be the newest (offer3).
        self.assertEqual(trade_offers[0].pk, offer3.pk)
        self.assertEqual(trade_offers[1].pk, offer2.pk)

        # Test pagination: second page should contain the remaining offer.
        response_page2 = self.client.get(url, {"order": "newest", "page": 2})
        self.assertEqual(response_page2.status_code, 200)
        trade_offers_page2 = response_page2.context.get("trade_offers")
        self.assertEqual(len(trade_offers_page2), 1)
        self.assertEqual(trade_offers_page2[0].pk, offer1.pk)

        # Test "oldest" ordering (ascending by updated_at).
        response_oldest = self.client.get(url, {"order": "oldest"})
        self.assertEqual(response_oldest.status_code, 200)
        trade_offers_oldest = response_oldest.context.get("trade_offers")
        self.assertEqual(len(trade_offers_oldest), 2)
        self.assertEqual(trade_offers_oldest[0].pk, offer1.pk)
        self.assertEqual(trade_offers_oldest[1].pk, offer2.pk)

    def test_trade_offer_want_list_view_pagination_and_ordering(self):
        """Test the want list view for correct pagination and ordering."""
        offer1 = self.create_trade_offer_for_want(updated_delta_minutes=1)
        offer2 = self.create_trade_offer_for_want(updated_delta_minutes=2)
        offer3 = self.create_trade_offer_for_want(updated_delta_minutes=3)

        url = reverse("cards:card_trade_offer_want_list", kwargs={"pk": self.card.pk})

        # Test order with "newest" first.
        response = self.client.get(url, {"order": "newest"})
        self.assertEqual(response.status_code, 200)
        trade_offers = response.context.get("trade_offers")
        self.assertEqual(response.context.get("side"), "want")
        self.assertEqual(len(trade_offers), 2)
        self.assertEqual(trade_offers[0].pk, offer3.pk)
        self.assertEqual(trade_offers[1].pk, offer2.pk)

        # Test pagination boundary on page 2.
        response_page2 = self.client.get(url, {"order": "newest", "page": 2})
        self.assertEqual(response_page2.status_code, 200)
        trade_offers_page2 = response_page2.context.get("trade_offers")
        self.assertEqual(len(trade_offers_page2), 1)
        self.assertEqual(trade_offers_page2[0].pk, offer1.pk)

        # Test ordering parameter for "oldest" ordering.
        response_oldest = self.client.get(url, {"order": "oldest"})
        self.assertEqual(response_oldest.status_code, 200)
        trade_offers_oldest = response_oldest.context.get("trade_offers")
        self.assertEqual(len(trade_offers_oldest), 2)
        self.assertEqual(trade_offers_oldest[0].pk, offer1.pk)
        self.assertEqual(trade_offers_oldest[1].pk, offer2.pk) 