"""
Tests for the Observations app.

Tests cover:
- Public submission (no login required)
- Tracking code uniqueness
- Internal pages require auth/permissions
- Status change logs are created
- Convert-to-action creates an action and links correctly
"""
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, Permission
from django.test import Client, TestCase
from django.urls import reverse

from apps.organizations.models import Department, Hospital

from .models import (
    Observation,
    ObservationCategory,
    ObservationNote,
    ObservationSeverity,
    ObservationStatus,
    ObservationStatusLog,
)
from .services import ObservationService

User = get_user_model()


class ObservationModelTests(TestCase):
    """Tests for Observation model."""

    def test_tracking_code_generated(self):
        """Test that tracking code is automatically generated."""
        observation = Observation.objects.create(
            description="Test observation"
        )
        self.assertIsNotNone(observation.tracking_code)
        self.assertTrue(observation.tracking_code.startswith('OBS-'))
        self.assertEqual(len(observation.tracking_code), 10)  # OBS-XXXXXX

    def test_tracking_code_unique(self):
        """Test that tracking codes are unique."""
        obs1 = Observation.objects.create(description="Test 1")
        obs2 = Observation.objects.create(description="Test 2")
        self.assertNotEqual(obs1.tracking_code, obs2.tracking_code)

    def test_is_anonymous_property(self):
        """Test is_anonymous property."""
        # Anonymous observation
        obs_anon = Observation.objects.create(description="Anonymous")
        self.assertTrue(obs_anon.is_anonymous)

        # Identified by staff ID
        obs_staff = Observation.objects.create(
            description="With staff ID",
            reporter_staff_id="12345"
        )
        self.assertFalse(obs_staff.is_anonymous)

        # Identified by name
        obs_name = Observation.objects.create(
            description="With name",
            reporter_name="John Doe"
        )
        self.assertFalse(obs_name.is_anonymous)

    def test_severity_color(self):
        """Test severity color method."""
        observation = Observation.objects.create(
            description="Test",
            severity=ObservationSeverity.HIGH
        )
        self.assertEqual(observation.get_severity_color(), 'danger')

    def test_status_color(self):
        """Test status color method."""
        observation = Observation.objects.create(
            description="Test",
            status=ObservationStatus.NEW
        )
        self.assertEqual(observation.get_status_color(), 'primary')


class ObservationCategoryTests(TestCase):
    """Tests for ObservationCategory model."""

    def test_category_creation(self):
        """Test category creation."""
        category = ObservationCategory.objects.create(
            name_en="Test Category",
            name_ar="فئة اختبار"
        )
        self.assertEqual(str(category), "Test Category")
        self.assertEqual(category.name, "Test Category")


class PublicViewTests(TestCase):
    """Tests for public views (no login required)."""

    def setUp(self):
        self.client = Client()
        self.category = ObservationCategory.objects.create(
            name_en="Test Category",
            is_active=True
        )

    def test_public_form_accessible(self):
        """Test that public form is accessible without login."""
        response = self.client.get(reverse('observations:observation_create_public'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Report an Observation')

    def test_public_submission_creates_observation(self):
        """Test that public submission creates an observation."""
        data = {
            'description': 'This is a test observation with enough detail.',
            'severity': 'medium',
            'category': self.category.id,
        }
        response = self.client.post(
            reverse('observations:observation_create_public'),
            data
        )
        
        # Should redirect to success page
        self.assertEqual(response.status_code, 302)
        
        # Observation should be created
        self.assertEqual(Observation.objects.count(), 1)
        observation = Observation.objects.first()
        self.assertEqual(observation.description, data['description'])
        self.assertEqual(observation.status, ObservationStatus.NEW)

    def test_public_submission_anonymous(self):
        """Test anonymous submission."""
        data = {
            'description': 'Anonymous observation test with details.',
            'severity': 'low',
        }
        response = self.client.post(
            reverse('observations:observation_create_public'),
            data
        )
        
        self.assertEqual(response.status_code, 302)
        observation = Observation.objects.first()
        self.assertTrue(observation.is_anonymous)

    def test_public_submission_with_reporter_info(self):
        """Test submission with reporter information."""
        data = {
            'description': 'Observation with reporter info and details.',
            'severity': 'medium',
            'reporter_staff_id': '12345',
            'reporter_name': 'John Doe',
        }
        response = self.client.post(
            reverse('observations:observation_create_public'),
            data
        )
        
        self.assertEqual(response.status_code, 302)
        observation = Observation.objects.first()
        self.assertFalse(observation.is_anonymous)
        self.assertEqual(observation.reporter_staff_id, '12345')
        self.assertEqual(observation.reporter_name, 'John Doe')

    def test_success_page_shows_tracking_code(self):
        """Test success page displays tracking code."""
        observation = Observation.objects.create(
            description="Test observation"
        )
        response = self.client.get(
            reverse('observations:observation_submitted', 
                    kwargs={'tracking_code': observation.tracking_code})
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, observation.tracking_code)

    def test_track_page_accessible(self):
        """Test tracking page is accessible."""
        response = self.client.get(reverse('observations:observation_track'))
        self.assertEqual(response.status_code, 200)

    def test_track_observation_by_code(self):
        """Test tracking observation by code."""
        observation = Observation.objects.create(
            description="Test observation"
        )
        response = self.client.get(
            reverse('observations:observation_track'),
            {'tracking_code': observation.tracking_code}
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, observation.tracking_code)

    def test_honeypot_blocks_spam(self):
        """Test honeypot field blocks spam submissions."""
        data = {
            'description': 'Spam observation with enough detail here.',
            'severity': 'medium',
            'website': 'spam-value',  # Honeypot field
        }
        response = self.client.post(
            reverse('observations:observation_create_public'),
            data
        )
        
        # Should not create observation
        self.assertEqual(Observation.objects.count(), 0)


class InternalViewTests(TestCase):
    """Tests for internal views (login required)."""

    def setUp(self):
        self.client = Client()
        
        # Create hospital and department
        self.hospital = Hospital.objects.create(
            name="Test Hospital",
            code="TH001"
        )
        self.department = Department.objects.create(
            name="Test Department",
            hospital=self.hospital,
            status='active'
        )
        
        # Create users
        self.admin_user = User.objects.create_user(
            email='admin@test.com',
            password='testpass123',
            is_staff=True,
            hospital=self.hospital
        )
        
        self.regular_user = User.objects.create_user(
            email='user@test.com',
            password='testpass123',
            hospital=self.hospital
        )
        
        # Create PX Admin group and add admin user
        px_admin_group, _ = Group.objects.get_or_create(name='PX Admin')
        self.admin_user.groups.add(px_admin_group)
        
        # Create observation
        self.observation = Observation.objects.create(
            description="Test observation for internal views"
        )

    def test_list_requires_login(self):
        """Test that list view requires login."""
        response = self.client.get(reverse('observations:observation_list'))
        self.assertEqual(response.status_code, 302)
        self.assertIn('login', response.url)

    def test_list_accessible_when_logged_in(self):
        """Test list view accessible when logged in."""
        self.client.login(email='admin@test.com', password='testpass123')
        response = self.client.get(reverse('observations:observation_list'))
        self.assertEqual(response.status_code, 200)

    def test_detail_requires_login(self):
        """Test that detail view requires login."""
        response = self.client.get(
            reverse('observations:observation_detail', 
                    kwargs={'pk': self.observation.id})
        )
        self.assertEqual(response.status_code, 302)

    def test_detail_accessible_when_logged_in(self):
        """Test detail view accessible when logged in."""
        self.client.login(email='admin@test.com', password='testpass123')
        response = self.client.get(
            reverse('observations:observation_detail', 
                    kwargs={'pk': self.observation.id})
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, self.observation.tracking_code)


class ObservationServiceTests(TestCase):
    """Tests for ObservationService."""

    def setUp(self):
        self.hospital = Hospital.objects.create(
            name="Test Hospital",
            code="TH001"
        )
        self.department = Department.objects.create(
            name="Test Department",
            hospital=self.hospital,
            status='active'
        )
        self.user = User.objects.create_user(
            email='test@test.com',
            password='testpass123',
            hospital=self.hospital
        )
        self.category = ObservationCategory.objects.create(
            name_en="Test Category",
            is_active=True
        )

    def test_create_observation_service(self):
        """Test creating observation via service."""
        observation = ObservationService.create_observation(
            description="Service created observation",
            severity='high',
            category=self.category,
            reporter_name="Test Reporter"
        )
        
        self.assertIsNotNone(observation.id)
        self.assertEqual(observation.status, ObservationStatus.NEW)
        self.assertFalse(observation.is_anonymous)
        
        # Check status log was created
        self.assertEqual(observation.status_logs.count(), 1)
        log = observation.status_logs.first()
        self.assertEqual(log.to_status, ObservationStatus.NEW)

    def test_change_status_creates_log(self):
        """Test that changing status creates a log entry."""
        observation = Observation.objects.create(
            description="Test observation"
        )
        
        ObservationService.change_status(
            observation=observation,
            new_status=ObservationStatus.TRIAGED,
            changed_by=self.user,
            comment="Triaging this observation"
        )
        
        observation.refresh_from_db()
        self.assertEqual(observation.status, ObservationStatus.TRIAGED)
        self.assertIsNotNone(observation.triaged_at)
        
        # Check log
        log = observation.status_logs.filter(to_status=ObservationStatus.TRIAGED).first()
        self.assertIsNotNone(log)
        self.assertEqual(log.changed_by, self.user)
        self.assertEqual(log.comment, "Triaging this observation")

    def test_triage_observation(self):
        """Test triaging an observation."""
        observation = Observation.objects.create(
            description="Test observation"
        )
        
        ObservationService.triage_observation(
            observation=observation,
            triaged_by=self.user,
            assigned_department=self.department,
            assigned_to=self.user,
            note="Assigning to department"
        )
        
        observation.refresh_from_db()
        self.assertEqual(observation.assigned_department, self.department)
        self.assertEqual(observation.assigned_to, self.user)
        self.assertEqual(observation.status, ObservationStatus.ASSIGNED)
        
        # Check note was created
        self.assertTrue(observation.observation_notes.filter(note="Assigning to department").exists())

    def test_add_note(self):
        """Test adding a note to observation."""
        observation = Observation.objects.create(
            description="Test observation"
        )
        
        note = ObservationService.add_note(
            observation=observation,
            note="This is a test note",
            created_by=self.user,
            is_internal=True
        )
        
        self.assertIsNotNone(note.id)
        self.assertEqual(note.observation, observation)
        self.assertEqual(note.created_by, self.user)
        self.assertTrue(note.is_internal)


class StatusLogTests(TestCase):
    """Tests for status logging."""

    def setUp(self):
        self.user = User.objects.create_user(
            email='test@test.com',
            password='testpass123'
        )

    def test_status_log_created_on_status_change(self):
        """Test that status log is created when status changes."""
        observation = Observation.objects.create(
            description="Test observation"
        )
        
        # Change status
        ObservationService.change_status(
            observation=observation,
            new_status=ObservationStatus.IN_PROGRESS,
            changed_by=self.user
        )
        
        # Check log exists
        logs = ObservationStatusLog.objects.filter(observation=observation)
        self.assertEqual(logs.count(), 1)
        
        log = logs.first()
        self.assertEqual(log.from_status, ObservationStatus.NEW)
        self.assertEqual(log.to_status, ObservationStatus.IN_PROGRESS)
        self.assertEqual(log.changed_by, self.user)

    def test_multiple_status_changes_logged(self):
        """Test that multiple status changes are all logged."""
        observation = Observation.objects.create(
            description="Test observation"
        )
        
        # Multiple status changes
        statuses = [
            ObservationStatus.TRIAGED,
            ObservationStatus.ASSIGNED,
            ObservationStatus.IN_PROGRESS,
            ObservationStatus.RESOLVED,
        ]
        
        for status in statuses:
            ObservationService.change_status(
                observation=observation,
                new_status=status,
                changed_by=self.user
            )
        
        # Check all logs exist
        logs = ObservationStatusLog.objects.filter(observation=observation)
        self.assertEqual(logs.count(), len(statuses))


class CategoryManagementTests(TestCase):
    """Tests for category management."""

    def setUp(self):
        self.client = Client()
        self.user = User.objects.create_user(
            email='admin@test.com',
            password='testpass123',
            is_staff=True
        )
        
        # Add manage_categories permission
        permission = Permission.objects.get(codename='manage_categories')
        self.user.user_permissions.add(permission)

    def test_category_list_requires_permission(self):
        """Test category list requires permission."""
        self.client.login(email='admin@test.com', password='testpass123')
        response = self.client.get(reverse('observations:category_list'))
        self.assertEqual(response.status_code, 200)

    def test_category_create(self):
        """Test creating a category."""
        self.client.login(email='admin@test.com', password='testpass123')
        
        data = {
            'name_en': 'New Category',
            'name_ar': 'فئة جديدة',
            'sort_order': 1,
            'is_active': True,
        }
        
        response = self.client.post(
            reverse('observations:category_create'),
            data
        )
        
        self.assertEqual(response.status_code, 302)
        self.assertTrue(
            ObservationCategory.objects.filter(name_en='New Category').exists()
        )
