# -*- coding: utf-8 -*-
"""
Adtlas Activities Model Tests

This module contains comprehensive tests for the activities app models,
including ActivityCategory, Activity, and ActivitySummary models.

Test Coverage:
    - Model creation and validation
    - Model methods and properties
    - Model relationships
    - Custom managers
    - Signal handling
    - Data integrity

Author: Adtlas Development Team
Version: 1.0.0
Last Updated: 2025-01-27
"""

import json
from datetime import datetime, timedelta
from unittest.mock import patch, Mock

from django.test import TestCase, TransactionTestCase
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.utils import timezone
from django.http import HttpRequest

from ..models import ActivityCategory, Activity, ActivitySummary
from ..signals import activity_logged

# Get the user model
User = get_user_model()


class ActivityCategoryModelTest(TestCase):
    """
    Test cases for the ActivityCategory model.
    
    This class tests the ActivityCategory model functionality,
    including validation, methods, and relationships.
    """
    
    def setUp(self):
        """
        Set up test data for ActivityCategory tests.
        """
        self.category_data = {
            'name': 'Test Category',
            'code': 'test_category',
            'description': 'A test category for unit testing',
            'color': '#007bff',
            'icon': 'fas fa-test',
            'is_active': True
        }
    
    def test_create_activity_category(self):
        """
        Test creating a new ActivityCategory instance.
        """
        category = ActivityCategory.objects.create(**self.category_data)
        
        self.assertEqual(category.name, self.category_data['name'])
        self.assertEqual(category.code, self.category_data['code'])
        self.assertEqual(category.description, self.category_data['description'])
        self.assertEqual(category.color, self.category_data['color'])
        self.assertEqual(category.icon, self.category_data['icon'])
        self.assertTrue(category.is_active)
        self.assertIsNotNone(category.created_at)
        self.assertIsNotNone(category.updated_at)
    
    def test_activity_category_str_representation(self):
        """
        Test the string representation of ActivityCategory.
        """
        category = ActivityCategory.objects.create(**self.category_data)
        self.assertEqual(str(category), self.category_data['name'])
    
    def test_activity_category_unique_code(self):
        """
        Test that ActivityCategory code must be unique.
        """
        ActivityCategory.objects.create(**self.category_data)
        
        # Try to create another category with the same code
        with self.assertRaises(IntegrityError):
            ActivityCategory.objects.create(
                name='Another Category',
                code=self.category_data['code'],  # Same code
                description='Another description'
            )
    
    def test_activity_category_color_validation(self):
        """
        Test color field validation for ActivityCategory.
        """
        # Test valid color
        category = ActivityCategory(**self.category_data)
        category.full_clean()  # Should not raise ValidationError
        
        # Test invalid color format
        invalid_data = self.category_data.copy()
        invalid_data['color'] = 'invalid-color'
        
        category = ActivityCategory(**invalid_data)
        with self.assertRaises(ValidationError):
            category.full_clean()
    
    def test_activity_category_ordering(self):
        """
        Test the default ordering of ActivityCategory.
        """
        # Create categories with different names
        category1 = ActivityCategory.objects.create(
            name='Z Category', code='z_category'
        )
        category2 = ActivityCategory.objects.create(
            name='A Category', code='a_category'
        )
        
        categories = list(ActivityCategory.objects.all())
        self.assertEqual(categories[0], category2)  # A Category should come first
        self.assertEqual(categories[1], category1)  # Z Category should come second
    
    def test_activity_category_active_manager(self):
        """
        Test the active manager for ActivityCategory.
        """
        # Create active and inactive categories
        active_category = ActivityCategory.objects.create(
            name='Active Category', code='active', is_active=True
        )
        inactive_category = ActivityCategory.objects.create(
            name='Inactive Category', code='inactive', is_active=False
        )
        
        # Test active manager
        active_categories = ActivityCategory.active.all()
        self.assertIn(active_category, active_categories)
        self.assertNotIn(inactive_category, active_categories)


class ActivityModelTest(TestCase):
    """
    Test cases for the Activity model.
    
    This class tests the Activity model functionality,
    including creation, validation, methods, and relationships.
    """
    
    def setUp(self):
        """
        Set up test data for Activity tests.
        """
        self.user = User.objects.create_user(
            email='test@example.com',
            password='testpass123'
        )
        
        self.category = ActivityCategory.objects.create(
            name='Test Category',
            code='test_category'
        )
        
        self.content_type = ContentType.objects.get_for_model(User)
        
        self.activity_data = {
            'user': self.user,
            'action': 'test_action',
            'description': 'Test activity description',
            'category': self.category,
            'content_type': self.content_type,
            'object_id': str(self.user.id),
            'ip_address': '192.168.1.1',
            'user_agent': 'Test User Agent',
            'is_successful': True,
            'duration_ms': 1500.0,
            'metadata': {'test_key': 'test_value'}
        }
    
    def test_create_activity(self):
        """
        Test creating a new Activity instance.
        """
        activity = Activity.objects.create(**self.activity_data)
        
        self.assertEqual(activity.user, self.user)
        self.assertEqual(activity.action, 'test_action')
        self.assertEqual(activity.description, 'Test activity description')
        self.assertEqual(activity.category, self.category)
        self.assertEqual(activity.content_type, self.content_type)
        self.assertEqual(activity.object_id, str(self.user.id))
        self.assertEqual(activity.ip_address, '192.168.1.1')
        self.assertEqual(activity.user_agent, 'Test User Agent')
        self.assertTrue(activity.is_successful)
        self.assertEqual(activity.duration_ms, 1500.0)
        self.assertEqual(activity.metadata, {'test_key': 'test_value'})
        self.assertIsNotNone(activity.created_at)
        self.assertIsNotNone(activity.updated_at)
    
    def test_activity_str_representation(self):
        """
        Test the string representation of Activity.
        """
        activity = Activity.objects.create(**self.activity_data)
        expected_str = f"{self.user.email} - test_action - {activity.created_at.strftime('%Y-%m-%d %H:%M')}"
        self.assertEqual(str(activity), expected_str)
    
    def test_activity_without_user(self):
        """
        Test creating an Activity without a user (system activity).
        """
        activity_data = self.activity_data.copy()
        activity_data['user'] = None
        
        activity = Activity.objects.create(**activity_data)
        self.assertIsNone(activity.user)
        
        expected_str = f"System - test_action - {activity.created_at.strftime('%Y-%m-%d %H:%M')}"
        self.assertEqual(str(activity), expected_str)
    
    def test_activity_get_action_display(self):
        """
        Test the get_action_display method.
        """
        activity = Activity.objects.create(**self.activity_data)
        
        # Test with underscore action
        self.assertEqual(activity.get_action_display(), 'Test Action')
        
        # Test with different action
        activity.action = 'user_login'
        self.assertEqual(activity.get_action_display(), 'User Login')
    
    def test_activity_get_content_object(self):
        """
        Test the get_content_object method.
        """
        activity = Activity.objects.create(**self.activity_data)
        content_object = activity.get_content_object()
        
        self.assertEqual(content_object, self.user)
    
    def test_activity_get_content_object_none(self):
        """
        Test get_content_object when content_type or object_id is None.
        """
        activity_data = self.activity_data.copy()
        activity_data['content_type'] = None
        activity_data['object_id'] = None
        
        activity = Activity.objects.create(**activity_data)
        content_object = activity.get_content_object()
        
        self.assertIsNone(content_object)
    
    def test_activity_is_recent_property(self):
        """
        Test the is_recent property.
        """
        # Create recent activity
        recent_activity = Activity.objects.create(**self.activity_data)
        self.assertTrue(recent_activity.is_recent)
        
        # Create old activity
        old_activity = Activity.objects.create(**self.activity_data)
        old_activity.created_at = timezone.now() - timedelta(hours=25)
        old_activity.save()
        
        self.assertFalse(old_activity.is_recent)
    
    def test_activity_duration_seconds_property(self):
        """
        Test the duration_seconds property.
        """
        activity = Activity.objects.create(**self.activity_data)
        self.assertEqual(activity.duration_seconds, 1.5)
        
        # Test with None duration
        activity.duration_ms = None
        self.assertIsNone(activity.duration_seconds)
    
    def test_activity_log_activity_class_method(self):
        """
        Test the log_activity class method.
        """
        # Mock request
        request = Mock(spec=HttpRequest)
        request.META = {
            'REMOTE_ADDR': '192.168.1.100',
            'HTTP_USER_AGENT': 'Test Browser'
        }
        
        with patch('apps.activities.models.get_client_ip', return_value='192.168.1.100'), \
             patch('apps.activities.models.get_user_agent', return_value='Test Browser'):
            
            activity = Activity.log_activity(
                user=self.user,
                action='test_log',
                description='Test log activity',
                category=self.category,
                request=request
            )
        
        self.assertEqual(activity.user, self.user)
        self.assertEqual(activity.action, 'test_log')
        self.assertEqual(activity.description, 'Test log activity')
        self.assertEqual(activity.category, self.category)
        self.assertEqual(activity.ip_address, '192.168.1.100')
        self.assertEqual(activity.user_agent, 'Test Browser')
    
    def test_activity_manager_recent(self):
        """
        Test the recent method of ActivityManager.
        """
        # Create activities with different timestamps
        recent_activity = Activity.objects.create(**self.activity_data)
        
        old_activity = Activity.objects.create(**self.activity_data)
        old_activity.created_at = timezone.now() - timedelta(hours=25)
        old_activity.save()
        
        recent_activities = Activity.objects.recent()
        self.assertIn(recent_activity, recent_activities)
        self.assertNotIn(old_activity, recent_activities)
    
    def test_activity_manager_successful(self):
        """
        Test the successful method of ActivityManager.
        """
        # Create successful and failed activities
        successful_activity = Activity.objects.create(**self.activity_data)
        
        failed_data = self.activity_data.copy()
        failed_data['is_successful'] = False
        failed_activity = Activity.objects.create(**failed_data)
        
        successful_activities = Activity.objects.successful()
        self.assertIn(successful_activity, successful_activities)
        self.assertNotIn(failed_activity, successful_activities)
    
    def test_activity_manager_failed(self):
        """
        Test the failed method of ActivityManager.
        """
        # Create successful and failed activities
        successful_activity = Activity.objects.create(**self.activity_data)
        
        failed_data = self.activity_data.copy()
        failed_data['is_successful'] = False
        failed_activity = Activity.objects.create(**failed_data)
        
        failed_activities = Activity.objects.failed()
        self.assertNotIn(successful_activity, failed_activities)
        self.assertIn(failed_activity, failed_activities)
    
    def test_activity_manager_by_user(self):
        """
        Test the by_user method of ActivityManager.
        """
        # Create another user
        other_user = User.objects.create_user(
            email='other@example.com',
            password='testpass123'
        )
        
        # Create activities for different users
        user_activity = Activity.objects.create(**self.activity_data)
        
        other_data = self.activity_data.copy()
        other_data['user'] = other_user
        other_activity = Activity.objects.create(**other_data)
        
        user_activities = Activity.objects.by_user(self.user)
        self.assertIn(user_activity, user_activities)
        self.assertNotIn(other_activity, user_activities)
    
    def test_activity_manager_by_action(self):
        """
        Test the by_action method of ActivityManager.
        """
        # Create activities with different actions
        login_data = self.activity_data.copy()
        login_data['action'] = 'login'
        login_activity = Activity.objects.create(**login_data)
        
        logout_data = self.activity_data.copy()
        logout_data['action'] = 'logout'
        logout_activity = Activity.objects.create(**logout_data)
        
        login_activities = Activity.objects.by_action('login')
        self.assertIn(login_activity, login_activities)
        self.assertNotIn(logout_activity, login_activities)
    
    def test_activity_manager_in_date_range(self):
        """
        Test the in_date_range method of ActivityManager.
        """
        # Create activities with different dates
        today = timezone.now().date()
        yesterday = today - timedelta(days=1)
        tomorrow = today + timedelta(days=1)
        
        today_activity = Activity.objects.create(**self.activity_data)
        
        yesterday_data = self.activity_data.copy()
        yesterday_activity = Activity.objects.create(**yesterday_data)
        yesterday_activity.created_at = timezone.make_aware(
            datetime.combine(yesterday, datetime.min.time())
        )
        yesterday_activity.save()
        
        # Test date range
        activities_in_range = Activity.objects.in_date_range(yesterday, today)
        self.assertIn(today_activity, activities_in_range)
        self.assertIn(yesterday_activity, activities_in_range)


class ActivitySummaryModelTest(TestCase):
    """
    Test cases for the ActivitySummary model.
    
    This class tests the ActivitySummary model functionality,
    including creation, validation, and methods.
    """
    
    def setUp(self):
        """
        Set up test data for ActivitySummary tests.
        """
        self.user = User.objects.create_user(
            email='test@example.com',
            password='testpass123'
        )
        
        self.category = ActivityCategory.objects.create(
            name='Test Category',
            code='test_category'
        )
        
        self.summary_data = {
            'date': timezone.now().date(),
            'user': self.user,
            'category': self.category,
            'total_activities': 100,
            'successful_activities': 90,
            'failed_activities': 10,
            'avg_duration_ms': 1500.0,
            'unique_ips': 5,
            'metadata': {'calculation_method': 'test'}
        }
    
    def test_create_activity_summary(self):
        """
        Test creating a new ActivitySummary instance.
        """
        summary = ActivitySummary.objects.create(**self.summary_data)
        
        self.assertEqual(summary.date, self.summary_data['date'])
        self.assertEqual(summary.user, self.user)
        self.assertEqual(summary.category, self.category)
        self.assertEqual(summary.total_activities, 100)
        self.assertEqual(summary.successful_activities, 90)
        self.assertEqual(summary.failed_activities, 10)
        self.assertEqual(summary.avg_duration_ms, 1500.0)
        self.assertEqual(summary.unique_ips, 5)
        self.assertEqual(summary.metadata, {'calculation_method': 'test'})
    
    def test_activity_summary_str_representation(self):
        """
        Test the string representation of ActivitySummary.
        """
        summary = ActivitySummary.objects.create(**self.summary_data)
        expected_str = f"{self.summary_data['date']} - {self.user.email} - {self.category.name}"
        self.assertEqual(str(summary), expected_str)
    
    def test_activity_summary_without_user_and_category(self):
        """
        Test ActivitySummary without user and category (overall summary).
        """
        summary_data = self.summary_data.copy()
        summary_data['user'] = None
        summary_data['category'] = None
        
        summary = ActivitySummary.objects.create(**summary_data)
        expected_str = f"{self.summary_data['date']} - Overall Summary"
        self.assertEqual(str(summary), expected_str)
    
    def test_activity_summary_success_rate_property(self):
        """
        Test the success_rate property.
        """
        summary = ActivitySummary.objects.create(**self.summary_data)
        self.assertEqual(summary.success_rate, 90.0)
        
        # Test with zero total activities
        summary.total_activities = 0
        self.assertEqual(summary.success_rate, 0.0)
    
    def test_activity_summary_failure_rate_property(self):
        """
        Test the failure_rate property.
        """
        summary = ActivitySummary.objects.create(**self.summary_data)
        self.assertEqual(summary.failure_rate, 10.0)
        
        # Test with zero total activities
        summary.total_activities = 0
        self.assertEqual(summary.failure_rate, 0.0)
    
    def test_activity_summary_avg_duration_seconds_property(self):
        """
        Test the avg_duration_seconds property.
        """
        summary = ActivitySummary.objects.create(**self.summary_data)
        self.assertEqual(summary.avg_duration_seconds, 1.5)
        
        # Test with None duration
        summary.avg_duration_ms = None
        self.assertIsNone(summary.avg_duration_seconds)
    
    def test_activity_summary_unique_constraint(self):
        """
        Test the unique constraint on date, user, and category.
        """
        ActivitySummary.objects.create(**self.summary_data)
        
        # Try to create another summary with the same date, user, and category
        with self.assertRaises(IntegrityError):
            ActivitySummary.objects.create(**self.summary_data)
    
    def test_activity_summary_ordering(self):
        """
        Test the default ordering of ActivitySummary.
        """
        today = timezone.now().date()
        yesterday = today - timedelta(days=1)
        
        # Create summaries with different dates
        today_summary = ActivitySummary.objects.create(**self.summary_data)
        
        yesterday_data = self.summary_data.copy()
        yesterday_data['date'] = yesterday
        yesterday_data['user'] = None  # Make it different to avoid unique constraint
        yesterday_summary = ActivitySummary.objects.create(**yesterday_data)
        
        summaries = list(ActivitySummary.objects.all())
        self.assertEqual(summaries[0], today_summary)  # Today should come first (desc order)
        self.assertEqual(summaries[1], yesterday_summary)  # Yesterday should come second


class ActivitySignalTest(TransactionTestCase):
    """
    Test cases for Activity signals.
    
    This class tests signal handling for the Activity model,
    including the activity_logged signal.
    """
    
    def setUp(self):
        """
        Set up test data for signal tests.
        """
        self.user = User.objects.create_user(
            email='test@example.com',
            password='testpass123'
        )
        
        self.category = ActivityCategory.objects.create(
            name='Test Category',
            code='test_category'
        )
    
    def test_activity_logged_signal(self):
        """
        Test that the activity_logged signal is sent when an activity is created.
        """
        signal_received = []
        
        def signal_handler(sender, activity, **kwargs):
            signal_received.append(activity)
        
        # Connect signal handler
        activity_logged.connect(signal_handler)
        
        try:
            # Create activity
            activity = Activity.objects.create(
                user=self.user,
                action='test_signal',
                description='Test signal activity',
                category=self.category
            )
            
            # Check that signal was received
            self.assertEqual(len(signal_received), 1)
            self.assertEqual(signal_received[0], activity)
            
        finally:
            # Disconnect signal handler
            activity_logged.disconnect(signal_handler)