from django.test import TestCase, Client
from django.contrib.auth import get_user_model
from rest_framework.test import APIClient
from rest_framework import status
from unittest.mock import patch, MagicMock
import json

from organization.models import Organization, SyncConfiguration, SyncLog
from organization.services.external_api_service import ExternalAPIService, APIError
from organization.services.sync_service import OrganizationSyncService

User = get_user_model()


class OrganizationModelTests(TestCase):
    """Tests for Organization model."""

    def test_create_organization(self):
        """Test creating an organization."""
        org = Organization.objects.create(
            name="Test University",
            code="TU",
            email="admin@test.edu",
            external_id="ext-001",
            external_source="test_api"
        )
        self.assertEqual(org.name, "Test University")
        self.assertEqual(org.slug, "test-university")
        self.assertTrue(org.is_active)

    def test_organization_slug_auto_generation(self):
        """Test slug is auto-generated."""
        org = Organization.objects.create(
            name="Another University",
            code="AU",
            email="admin@another.edu",
            external_id="ext-002",
            external_source="test_api"
        )
        self.assertEqual(org.slug, "another-university")

    def test_mark_synced(self):
        """Test mark_synced updates last_synced."""
        org = Organization.objects.create(
            name="Test Org",
            code="TO",
            email="test@org.edu",
            external_id="ext-003",
            external_source="test_api"
        )
        self.assertIsNone(org.last_synced)
        org.mark_synced()
        self.assertIsNotNone(org.last_synced)


class SyncConfigurationTests(TestCase):
    """Tests for SyncConfiguration model."""

    def setUp(self):
        self.config = SyncConfiguration.objects.create(
            source_name="test_source",
            base_url="https://api.example.com",
            api_endpoint="/api/organizations/",
            auth_type="api_key",
            auth_key="test-key-123"
        )

    def test_get_full_endpoint_url(self):
        """Test full URL generation."""
        expected_url = "https://api.example.com/api/organizations/"
        self.assertEqual(self.config.get_full_endpoint_url(), expected_url)

    def test_config_defaults(self):
        """Test configuration default values."""
        self.assertEqual(self.config.timeout_seconds, 30)
        self.assertEqual(self.config.max_retries, 3)
        self.assertTrue(self.config.cache_enabled)
        self.assertTrue(self.config.is_active)


class ExternalAPIServiceTests(TestCase):
    """Tests for ExternalAPIService."""

    def setUp(self):
        self.config = SyncConfiguration.objects.create(
            source_name="test_source",
            base_url="https://api.example.com",
            auth_type="api_key",
            auth_key="test-key-123"
        )
        self.service = ExternalAPIService(self.config)

    @patch("organization.services.external_api_service.requests.get")
    def test_fetch_organizations_success(self, mock_get):
        """Test successful fetch."""
        mock_response = MagicMock()
        mock_response.json.return_value = {
            "data": [
                {
                    "id": "1",
                    "name": "Test Org",
                    "code": "TO",
                    "email": "test@org.edu"
                }
            ]
        }
        mock_response.status_code = 200
        mock_get.return_value = mock_response

        result = self.service.fetch_organizations(use_cache=False)

        self.assertIn("data", result)
        self.assertEqual(len(result["data"]), 1)

    @patch("organization.services.external_api_service.requests.get")
    def test_fetch_organizations_with_retry(self, mock_get):
        """Test retry logic on failure."""
        # Fail twice, then succeed
        mock_get.side_effect = [
            Exception("Connection timeout"),
            Exception("Connection timeout"),
            MagicMock(json=lambda: {"data": []}, status_code=200)
        ]

        with self.assertRaises(Exception):
            # The current implementation will raise after max retries
            self.service.fetch_organizations(use_cache=False)


class OrganizationSyncServiceTests(TestCase):
    """Tests for OrganizationSyncService."""

    def setUp(self):
        self.config = SyncConfiguration.objects.create(
            source_name="test_source",
            base_url="https://api.example.com",
            auth_type="api_key",
            auth_key="test-key-123"
        )
        self.sync_service = OrganizationSyncService(self.config)
        self.sync_log = SyncLog.objects.create(
            external_source="test_source"
        )

    def test_map_external_data_success(self):
        """Test successful data mapping."""
        external_record = {
            "id": "ext-001",
            "name": "Test University",
            "code": "TU",
            "email": "admin@test.edu",
            "phone": "555-0000",
            "country": "USA"
        }

        mapped_data = self.sync_service._map_external_data(external_record)

        self.assertEqual(mapped_data["external_id"], "ext-001")
        self.assertEqual(mapped_data["name"], "Test University")
        self.assertEqual(mapped_data["external_source"], "test_source")

    def test_map_external_data_missing_required(self):
        """Test mapping fails with missing required fields."""
        external_record = {
            "id": "ext-001",
            "name": "Test University"
            # Missing code and email
        }

        with self.assertRaises(Exception):
            self.sync_service._map_external_data(external_record)

    def test_validate_organization_data(self):
        """Test validation of organization data."""
        valid_data = {
            "name": "Test Org",
            "code": "TO",
            "email": "test@org.edu",
            "external_id": "ext-001",
            "external_source": "test"
        }

        is_valid, error_msg = self.sync_service._validate_organization_data(valid_data)
        self.assertTrue(is_valid)
        self.assertIsNone(error_msg)

    def test_validate_fails_invalid_email(self):
        """Test validation fails with invalid email."""
        invalid_data = {
            "name": "Test Org",
            "code": "TO",
            "email": "not-an-email",
            "external_id": "ext-001",
            "external_source": "test"
        }

        is_valid, error_msg = self.sync_service._validate_organization_data(invalid_data)
        self.assertFalse(is_valid)


class OrganizationAPITests(TestCase):
    """Tests for Organization API endpoints."""

    def setUp(self):
        self.client = APIClient()
        self.admin_user = User.objects.create_superuser(
            username="admin",
            email="admin@test.edu",
            password="admin123"
        )
        self.normal_user = User.objects.create_user(
            username="user",
            email="user@test.edu",
            password="user123"
        )
        self.config = SyncConfiguration.objects.create(
            source_name="test_source",
            base_url="https://api.example.com",
            auth_type="api_key",
            auth_key="test-key-123"
        )

    def test_list_organizations_requires_auth(self):
        """Test list endpoint requires authentication."""
        response = self.client.get("/api/organizations/")
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_list_organizations_authenticated(self):
        """Test list endpoint with authentication."""
        self.client.force_authenticate(user=self.normal_user)
        response = self.client.get("/api/organizations/")
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    @patch("organization.services.sync_service.OrganizationSyncService.sync_organizations")
    def test_sync_endpoint_requires_admin(self, mock_sync):
        """Test sync endpoint requires admin."""
        self.client.force_authenticate(user=self.normal_user)
        response = self.client.post("/api/organizations/sync/", {})
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    @patch("organization.services.sync_service.OrganizationSyncService.sync_organizations")
    def test_sync_endpoint_admin_access(self, mock_sync):
        """Test sync endpoint with admin access."""
        self.client.force_authenticate(user=self.admin_user)
        response = self.client.post("/api/organizations/sync/", {})
        # Will fail on validation if request format is wrong, but not on auth
        self.assertIn(
            response.status_code,
            [status.HTTP_200_OK, status.HTTP_400_BAD_REQUEST]
        )
