from django.test import TestCase, Client, RequestFactory
from django.urls import reverse
from django.contrib.auth import get_user_model
from cards.models import Card, Deck
from trades.models import TradeOffer, TradeOfferHaveCard, TradeOfferWantCard
from accounts.models import FriendCode
from home.views import HomePageView
import json
from collections import OrderedDict
from unittest.mock import patch, MagicMock
from django.core.exceptions import ObjectDoesNotExist
import importlib
from tests.utils.rarity import RARITY_MAPPING

User = get_user_model()

class HomePageViewTests(TestCase):
    """Test suite for the HomePageView."""
    
    @classmethod
    def setUpTestData(cls):
        """Set up data for all test methods."""
        # Create a user
        cls.user = User.objects.create_user(
            username='testuser',
            email='testuser@example.com',
            password='testpass123'
        )
        
        # Create a friend code for the user
        cls.friend_code = FriendCode.objects.create(
            user=cls.user,
            friend_code='SW-1234-5678-9012',
            in_game_name='TestTrainer'
        )
        
        # Create decks
        cls.deck1 = Deck.objects.create(
            name='Test Deck 1',
            hex_color='#FF0000',
            cardset='TEST01'
        )
        
        # Create cards with different rarities
        cls.common_card = Card.objects.create(
            name='Common Test Card',
            cardset='TEST01',
            cardnum=1,
            style='normal',
            rarity_icon='★',
            rarity_level=1
        )
        cls.common_card.decks.add(cls.deck1)
        
        cls.rare_card = Card.objects.create(
            name='Rare Test Card',
            cardset='TEST01',
            cardnum=2,
            style='normal',
            rarity_icon='★★★',
            rarity_level=3
        )
        cls.rare_card.decks.add(cls.deck1)
        
        cls.ultra_rare_card = Card.objects.create(
            name='Ultra Rare Test Card',
            cardset='TEST01',
            cardnum=3,
            style='normal',
            rarity_icon='★★★★',
            rarity_level=4
        )
        cls.ultra_rare_card.decks.add(cls.deck1)
        
        # Create trade offers with consistent rarities
        cls.common_trade = TradeOffer.objects.create(
            initiated_by=cls.friend_code,
            rarity_icon=RARITY_MAPPING[1],
            rarity_level=1
        )
        
        cls.rare_trade = TradeOffer.objects.create(
            initiated_by=cls.friend_code,
            rarity_icon=RARITY_MAPPING[3],
            rarity_level=3
        )
        
        # Add have and want cards with the SAME rarity for each trade
        TradeOfferHaveCard.objects.create(
            trade_offer=cls.common_trade,
            card=cls.common_card,
            quantity=2
        )
        
        TradeOfferHaveCard.objects.create(
            trade_offer=cls.rare_trade,
            card=cls.rare_card,
            quantity=1
        )
        
        # Add want cards with the SAME rarity as the have cards for each trade
        TradeOfferWantCard.objects.create(
            trade_offer=cls.common_trade,
            card=cls.common_card,
            quantity=1
        )
        
        TradeOfferWantCard.objects.create(
            trade_offer=cls.rare_trade,
            card=cls.rare_card,  # Changed from ultra_rare_card to match the rarity
            quantity=1
        )
        
    def setUp(self):
        """Set up before each test method."""
        self.client = Client()
        self.url = reverse('home')
        self.factory = RequestFactory()
        
    def test_home_page_status_code(self):
        """Test that the home page returns a 200 status code."""
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, 200)
        
    def test_home_page_template(self):
        """Test that the home page uses the correct template."""
        response = self.client.get(self.url)
        self.assertTemplateUsed(response, 'home/home.html')
        
    def test_home_page_context_cards(self):
        """Test that the home page contains all cards in the context."""
        response = self.client.get(self.url)
        self.assertIn('cards', response.context)
        self.assertEqual(response.context['cards'].count(), 3)
        
    def test_home_page_context_recent_offers(self):
        """Test that the home page contains recent offers in the context."""
        response = self.client.get(self.url)
        self.assertIn('recent_offers', response.context)
        self.assertEqual(len(response.context['recent_offers']), 2)
        # Recent offers should be ordered by most recent first
        self.assertEqual(response.context['recent_offers'][0], self.rare_trade)
        
    def test_home_page_context_most_offered_cards(self):
        """Test that the home page contains most offered cards in the context."""
        response = self.client.get(self.url)
        self.assertIn('most_offered_cards', response.context)
        most_offered = list(response.context['most_offered_cards'])
        self.assertEqual(len(most_offered), 2)
        # Common card should be most offered (quantity of 2)
        self.assertEqual(most_offered[0], self.common_card)
        
    def test_home_page_context_most_wanted_cards(self):
        """Test that the home page contains most wanted cards in the context."""
        response = self.client.get(self.url)
        self.assertIn('most_wanted_cards', response.context)
        most_wanted = list(response.context['most_wanted_cards'])
        self.assertEqual(len(most_wanted), 2)
        
    def test_home_page_context_least_offered_cards(self):
        """Test that the home page contains least offered cards in the context."""
        response = self.client.get(self.url)
        self.assertIn('least_offered_cards', response.context)
        
    def test_home_page_context_featured_offers(self):
        """Test that the home page contains featured offers in the context."""
        response = self.client.get(self.url)
        self.assertIn('featured_offers', response.context)
        featured = response.context['featured_offers']
        # Should be an OrderedDict
        self.assertIsInstance(featured, OrderedDict)
        # Should contain "All" category
        self.assertIn("All", featured)
        # Should contain both rarity icons
        self.assertIn('★★★', featured)
        self.assertIn('★', featured)
        # Higher rarity should come before lower rarity
        keys = list(featured.keys())
        # First key should be "All"
        self.assertEqual(keys[0], "All")
        # Higher rarity (★★★) should come before lower rarity (★)
        self.assertIn('★★★', keys)
        self.assertIn('★', keys)
        self.assertTrue(keys.index('★★★') < keys.index('★'))
        
    def test_closed_offers_not_shown(self):
        """Test that closed offers are not shown on the home page."""
        # Close one of the trade offers
        self.common_trade.is_closed = True
        self.common_trade.save()
        
        response = self.client.get(self.url)
        recent_offers = response.context['recent_offers']
        # Should only show the rare trade now
        self.assertEqual(len(recent_offers), 1)
        self.assertEqual(recent_offers[0], self.rare_trade)
        
    def test_home_page_with_no_data(self):
        """Test home page rendering when there's no trade data."""
        # Delete all trade offers
        TradeOffer.objects.all().delete()
        
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, 200)
        # Should have empty lists for offers
        self.assertEqual(len(response.context['recent_offers']), 0)
        
    def test_home_page_with_authenticated_user(self):
        """Test that the home page works for authenticated users."""
        self.client.login(username='testuser', password='testpass123')
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, 200)
        
    def test_rarity_sorting_in_featured_offers(self):
        """Test that offers are sorted by rarity level in descending order."""
        # Create a new ultra rare trade with consistent rarity
        ultra_trade = TradeOffer.objects.create(
            initiated_by=self.friend_code,
            rarity_icon='★★★★',
            rarity_level=4
        )
        
        # Add have and want cards with the same rarity
        TradeOfferHaveCard.objects.create(
            trade_offer=ultra_trade,
            card=self.ultra_rare_card,
            quantity=1
        )
        
        TradeOfferWantCard.objects.create(
            trade_offer=ultra_trade,
            card=self.ultra_rare_card,
            quantity=1
        )
        
        response = self.client.get(self.url)
        featured = response.context['featured_offers']
        keys = list(featured.keys())
        
        # Order should be: "All", "★★★★" (level 4), "★★★" (level 3), "★" (level 1)
        self.assertEqual(keys[0], "All")
        self.assertEqual(keys[1], "★★★★")
        self.assertEqual(keys[2], "★★★")
        self.assertEqual(keys[3], "★")


class HomePageViewMockTests(TestCase):
    """Test suite using mocks for HomePageView."""
    
    def setUp(self):
        self.factory = RequestFactory()
        self.view = HomePageView()
        
    @patch('trades.models.TradeOffer.objects')
    @patch('cards.models.Card.objects')
    def test_get_context_data_with_mocks(self, mock_card_objects, mock_offer_objects):
        """Test get_context_data using mocks."""
        # Set up request
        request = self.factory.get(reverse('home'))
        self.view.request = request
        
        # Mock the queryset responses
        mock_offer_filter = MagicMock()
        mock_offer_objects.filter.return_value = mock_offer_filter
        mock_offer_filter.order_by.return_value = []
        
        mock_card_filter = MagicMock()
        mock_card_objects.filter.return_value = mock_card_filter
        mock_card_objects.annotate.return_value = mock_card_filter
        mock_card_objects.all.return_value.order_by.return_value = []
        mock_card_filter.annotate.return_value = mock_card_filter
        mock_card_filter.order_by.return_value = []
        
        mock_offer_filter.values_list.return_value.distinct.return_value = []
        
        # Call the method
        context = self.view.get_context_data()
        
        # Verify the expected context keys exist
        self.assertIn('cards', context)
        self.assertIn('recent_offers', context)
        self.assertIn('most_offered_cards', context)
        self.assertIn('most_wanted_cards', context)
        self.assertIn('least_offered_cards', context)
        self.assertIn('featured_offers', context)
        
    @patch('trades.models.TradeOffer.objects')
    def test_empty_featured_offers(self, mock_offer_objects):
        """Test handling of empty featured offers."""
        # Set up request
        request = self.factory.get(reverse('home'))
        self.view.request = request
        
        # Configure mock to return empty queryset
        mock_offer_filter = MagicMock()
        mock_offer_objects.filter.return_value = mock_offer_filter
        mock_offer_filter.order_by.return_value = []
        mock_offer_filter.values_list.return_value.distinct.return_value = []
        
        # Call the method
        context = self.view.get_context_data()
        
        # Verify the featured_offers is an OrderedDict but with just the "All" key
        self.assertIsInstance(context['featured_offers'], OrderedDict)
        self.assertIn("All", context['featured_offers'])
        self.assertEqual(len(context['featured_offers']), 1)
        
    @patch('trades.models.TradeOffer.objects.filter')
    def test_exception_handling(self, mock_filter):
        """Test that exceptions are handled gracefully."""
        # Set up request
        request = self.factory.get(reverse('home'))
        self.view.request = request
        
        # Configure mock to raise an exception
        mock_filter.side_effect = Exception("Database error")
        
        # Call the method - should not raise an exception
        with self.assertLogs(level='ERROR') as cm:
            context = self.view.get_context_data()
            
            # Check if error was logged
            self.assertIn("Unhandled error in HomePageView.get_context_data", cm.output[0])
            
            # Verify fallback values were set
            self.assertEqual(len(context['cards']), 0)
            self.assertEqual(len(context['recent_offers']), 0)
            self.assertEqual(len(context['most_offered_cards']), 0)
            self.assertEqual(len(context['most_wanted_cards']), 0)
            self.assertEqual(len(context['least_offered_cards']), 0)
            self.assertIsInstance(context['featured_offers'], OrderedDict)
            self.assertEqual(len(context['featured_offers']), 1)
            self.assertIn("All", context['featured_offers'])

class HomePageEdgeCaseTests(TestCase):
    """Test edge cases for the home page."""
    
    def setUp(self):
        self.client = Client()
        self.url = reverse('home')
        
        # Create a user
        self.user = User.objects.create_user(
            username='testuser',
            email='testuser@example.com',
            password='testpass123'
        )
        
        # Create a friend code for the user
        self.friend_code = FriendCode.objects.create(
            user=self.user,
            friend_code='SW-1234-5678-9012',
            in_game_name='TestTrainer'
        )
    
    def test_home_page_with_no_cards(self):
        """Test home page with no cards in the database."""
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.context['cards']), 0)
        
    def test_home_page_with_many_offers(self):
        """Test home page with many offers to verify pagination or limiting works."""
        # Create a card
        card = Card.objects.create(
            name='Test Card',
            cardset='TEST01',
            cardnum=1,
            style='normal',
            rarity_icon='★',
            rarity_level=1
        )
        
        # Create 20 trade offers
        for i in range(20):
            trade = TradeOffer.objects.create(
                initiated_by=self.friend_code,
                rarity_icon='★',
                rarity_level=1
            )
            
            # Add have and want cards
            TradeOfferHaveCard.objects.create(
                trade_offer=trade,
                card=card,
                quantity=1
            )
            
            TradeOfferWantCard.objects.create(
                trade_offer=trade,
                card=card,
                quantity=1
            )
            
        response = self.client.get(self.url)
        
        # Check that recent_offers is limited to 6 as per the view
        self.assertEqual(len(response.context['recent_offers']), 6)
        
    def test_home_page_with_invalid_parameters(self):
        """Test home page with invalid GET parameters."""
        # The view should ignore invalid parameters
        response = self.client.get(f"{self.url}?invalid=param&another=invalid")
        self.assertEqual(response.status_code, 200)
        
    def test_performance_with_large_dataset(self):
        """Test performance with a larger dataset (basic check)."""
        # Create a card
        card = Card.objects.create(
            name='Performance Test Card',
            cardset='PERF01',
            cardnum=1,
            style='normal',
            rarity_icon='★',
            rarity_level=1
        )
        
        # Create 50 trade offers with different rarities
        for i in range(50):
            rarity_level = (i % 5) + 1  # 1-5
            rarity_icon = '★' * rarity_level
            
            trade = TradeOffer.objects.create(
                initiated_by=self.friend_code,
                rarity_icon=rarity_icon,
                rarity_level=rarity_level
            )
            
            # Add have and want cards with the same rarity
            rarity_card = Card.objects.create(
                name=f'Performance Test Card {i}',
                cardset='PERF01',
                cardnum=i+10,
                style='normal',
                rarity_icon=rarity_icon,
                rarity_level=rarity_level
            )
            
            TradeOfferHaveCard.objects.create(
                trade_offer=trade,
                card=rarity_card,
                quantity=1
            )
            
            TradeOfferWantCard.objects.create(
                trade_offer=trade,
                card=rarity_card,
                quantity=1
            )
            
        # Basic performance test - just checking it completes without timeout
        import time
        start = time.time()
        response = self.client.get(self.url)
        end = time.time()
        
        self.assertEqual(response.status_code, 200)
        
        # Should be reasonably fast (adjust threshold as needed)
        execution_time = end - start
        self.assertLess(execution_time, 2.0)  # Should complete in under 2 seconds


class TemplateRenderingTests(TestCase):
    """Tests focused on template rendering."""
    
    @classmethod
    def setUpTestData(cls):
        # Create a user
        cls.user = User.objects.create_user(
            username='testuser',
            email='testuser@example.com',
            password='testpass123'
        )
        
        # Create a friend code for the user
        cls.friend_code = FriendCode.objects.create(
            user=cls.user,
            friend_code='SW-1234-5678-9012',
            in_game_name='TestTrainer'
        )
        
        # Create a card
        cls.card = Card.objects.create(
            name='Test Card',
            cardset='TEST01',
            cardnum=1,
            style='normal',
            rarity_icon='★',
            rarity_level=1
        )
        
        # Create a trade offer
        cls.trade = TradeOffer.objects.create(
            initiated_by=cls.friend_code,
            rarity_icon='★',
            rarity_level=1
        )
        
        # Add have and want cards
        TradeOfferHaveCard.objects.create(
            trade_offer=cls.trade,
            card=cls.card,
            quantity=1
        )
        
        TradeOfferWantCard.objects.create(
            trade_offer=cls.trade,
            card=cls.card,
            quantity=1
        )
    
    def setUp(self):
        self.client = Client()
        self.factory = RequestFactory()
        
    def test_template_used(self):
        """Test that the correct template is used."""
        response = self.client.get(reverse('home'))
        self.assertTemplateUsed(response, 'home/home.html')
        
    def test_context_variables_exist(self):
        """Test that all expected context variables exist."""
        response = self.client.get(reverse('home'))
        
        # Check all required context variables
        expected_keys = [
            'cards',
            'recent_offers',
            'most_offered_cards',
            'most_wanted_cards',
            'least_offered_cards',
            'featured_offers',
        ]
        
        for key in expected_keys:
            self.assertIn(key, response.context)
            
    def test_view_with_pagination_params(self):
        """Test that view handles pagination parameters correctly, if applicable."""
        # Create additional trade offers if pagination is implemented
        for i in range(10):
            trade = TradeOffer.objects.create(
                initiated_by=self.friend_code,
                rarity_icon='★',
                rarity_level=1
            )
            
            # Add have and want cards
            TradeOfferHaveCard.objects.create(
                trade_offer=trade,
                card=self.card,
                quantity=1
            )
            
            TradeOfferWantCard.objects.create(
                trade_offer=trade,
                card=self.card,
                quantity=1
            )
            
        # Test with page parameter
        response = self.client.get(f"{reverse('home')}?page=1")
        self.assertEqual(response.status_code, 200)
        
        # Test with invalid page parameter
        response = self.client.get(f"{reverse('home')}?page=999")
        self.assertEqual(response.status_code, 200)  # Should still render with default page
        
        # Test with non-numeric page parameter
        response = self.client.get(f"{reverse('home')}?page=abc")
        self.assertEqual(response.status_code, 200)  # Should handle gracefully
        
    @patch('home.views.HomePageView.get_context_data')
    def test_view_renders_with_missing_context(self, mock_get_context):
        """Test that view renders even with incomplete context data."""
        # Return incomplete context
        mock_get_context.return_value = {'cards': []}
        
        # Should still render without error even with missing context variables
        response = self.client.get(reverse('home'))
        self.assertEqual(response.status_code, 200)
        
    def test_compatibility_with_multiple_django_versions(self):
        """Ensure compatibility with different Django versions."""
        import django
        # Simply log the Django version - the test itself verifies the page renders
        # with the current version
        django_version = django.get_version()
        response = self.client.get(reverse('home'))
        self.assertEqual(response.status_code, 200)
