import json

import pytest
from django.urls import reverse

from django_lumen.models import DiagramPreferences


# ── helpers ───────────────────────────────────────────────────────────────────

DIAGRAM_URL       = reverse('django-lumen-diagram')
SAVE_PREFS_URL    = reverse('django-lumen-save-preferences')
EXCLUDED_APPS_URL = reverse('django-lumen-excluded-apps')


# ── DiagramView ───────────────────────────────────────────────────────────────

@pytest.mark.django_db
class TestDiagramView:
    def test_anonymous_user_is_redirected(self, client):
        response = client.get(DIAGRAM_URL)
        assert response.status_code == 302

    def test_authenticated_non_staff_gets_403(self, client, regular_user):
        client.force_login(regular_user)
        response = client.get(DIAGRAM_URL)
        assert response.status_code == 403

    def test_inactive_staff_is_redirected(self, client, django_user_model):
        # Django's ModelBackend.get_user() excludes inactive users, so they are
        # treated as AnonymousUser by the auth middleware → redirect, not 403.
        user = django_user_model.objects.create_user(
            username='inactive_staff', password='pw', is_staff=True, is_active=False
        )
        client.force_login(user)
        response = client.get(DIAGRAM_URL)
        assert response.status_code == 302

    def test_staff_user_gets_200(self, staff_client):
        response = staff_client.get(DIAGRAM_URL)
        assert response.status_code == 200

    def test_response_contains_diagram_data(self, staff_client):
        response = staff_client.get(DIAGRAM_URL)
        diagram_data = json.loads(response.context['diagram_data'])
        assert 'models' in diagram_data

    def test_response_contains_prefs(self, staff_client):
        response = staff_client.get(DIAGRAM_URL)
        prefs = json.loads(response.context['prefs'])
        assert 'selected_apps' in prefs
        assert 'excluded_apps' in prefs
        assert 'routing_style' in prefs

    def test_default_prefs_used_when_no_record(self, staff_client, staff_user):
        response = staff_client.get(DIAGRAM_URL)
        prefs = json.loads(response.context['prefs'])
        assert prefs['routing_style'] == 'curved'
        assert 'auth' in prefs['excluded_apps']

    def test_saved_prefs_are_loaded(self, staff_client, staff_user):
        DiagramPreferences.objects.create(
            user=staff_user, routing_style='direct', excluded_apps=['auth'],
        )
        response = staff_client.get(DIAGRAM_URL)
        prefs = json.loads(response.context['prefs'])
        assert prefs['routing_style'] == 'direct'
        assert prefs['excluded_apps'] == ['auth']

    def test_excluded_apps_filter_diagram_data(self, staff_client, staff_user):
        # With 'tests' NOT excluded, tests models should appear in diagram data
        DiagramPreferences.objects.create(
            user=staff_user, excluded_apps=['auth', 'admin', 'contenttypes', 'sessions', 'django_lumen']
        )
        response = staff_client.get(DIAGRAM_URL)
        diagram_data = json.loads(response.context['diagram_data'])
        app_labels = {m['app'] for m in diagram_data['models']}
        assert 'tests' in app_labels

    def test_excluded_app_absent_from_diagram_data(self, staff_client, staff_user):
        DiagramPreferences.objects.create(
            user=staff_user,
            excluded_apps=['auth', 'admin', 'contenttypes', 'sessions', 'django_lumen', 'tests'],
        )
        response = staff_client.get(DIAGRAM_URL)
        diagram_data = json.loads(response.context['diagram_data'])
        app_labels = {m['app'] for m in diagram_data['models']}
        assert 'tests' not in app_labels


# ── SavePreferencesView ───────────────────────────────────────────────────────

@pytest.mark.django_db
class TestSavePreferencesView:
    def test_anonymous_is_redirected(self, client):
        response = client.post(SAVE_PREFS_URL, content_type='application/json', data='{}')
        assert response.status_code == 302

    def test_non_staff_gets_403(self, client, regular_user):
        client.force_login(regular_user)
        response = client.post(SAVE_PREFS_URL, content_type='application/json', data='{}')
        assert response.status_code == 403

    def test_creates_preferences_record(self, staff_client, staff_user):
        payload = {'selected_apps': ['myapp'], 'excluded_apps': ['auth'], 'routing_style': 'direct'}
        response = staff_client.post(
            SAVE_PREFS_URL, data=json.dumps(payload), content_type='application/json'
        )
        assert response.status_code == 200
        assert response.json() == {'ok': True}
        prefs = DiagramPreferences.objects.get(user=staff_user)
        assert prefs.selected_apps == ['myapp']
        assert prefs.excluded_apps == ['auth']
        assert prefs.routing_style == 'direct'

    def test_updates_existing_preferences_record(self, staff_client, staff_user):
        DiagramPreferences.objects.create(user=staff_user, routing_style='curved')
        payload = {'selected_apps': [], 'excluded_apps': [], 'routing_style': 'orthogonal'}
        staff_client.post(SAVE_PREFS_URL, data=json.dumps(payload), content_type='application/json')
        assert DiagramPreferences.objects.filter(user=staff_user).count() == 1
        assert DiagramPreferences.objects.get(user=staff_user).routing_style == 'orthogonal'

    def test_missing_fields_use_defaults(self, staff_client, staff_user):
        staff_client.post(SAVE_PREFS_URL, data='{}', content_type='application/json')
        prefs = DiagramPreferences.objects.get(user=staff_user)
        assert prefs.selected_apps == []
        assert prefs.excluded_apps == []
        assert prefs.routing_style == 'curved'


# ── ExcludedAppsView ──────────────────────────────────────────────────────────

@pytest.mark.django_db
class TestExcludedAppsView:
    def test_anonymous_is_redirected(self, client):
        response = client.get(EXCLUDED_APPS_URL)
        assert response.status_code == 302

    def test_non_staff_gets_403(self, client, regular_user):
        client.force_login(regular_user)
        response = client.get(EXCLUDED_APPS_URL)
        assert response.status_code == 403

    def test_staff_gets_200(self, staff_client):
        response = staff_client.get(EXCLUDED_APPS_URL)
        assert response.status_code == 200

    def test_context_has_all_apps(self, staff_client):
        response = staff_client.get(EXCLUDED_APPS_URL)
        # Should include system apps (auth, contenttypes, etc.)
        assert 'auth' in response.context['all_apps']
        assert 'tests' in response.context['all_apps']

    def test_context_excluded_apps_from_prefs(self, staff_client, staff_user):
        DiagramPreferences.objects.create(user=staff_user, excluded_apps=['auth', 'sessions'])
        response = staff_client.get(EXCLUDED_APPS_URL)
        assert response.context['excluded_apps'] == ['auth', 'sessions']

    def test_post_updates_excluded_apps(self, staff_client, staff_user):
        response = staff_client.post(EXCLUDED_APPS_URL, data={'excluded_apps': ['auth', 'sessions']})
        assert response.status_code == 302
        prefs = DiagramPreferences.objects.get(user=staff_user)
        assert 'auth' in prefs.excluded_apps
        assert 'sessions' in prefs.excluded_apps

    def test_post_rejects_unknown_apps(self, staff_client, staff_user):
        # 'nonexistent_app' should be silently dropped
        staff_client.post(EXCLUDED_APPS_URL, data={'excluded_apps': ['auth', 'nonexistent_app']})
        prefs = DiagramPreferences.objects.get(user=staff_user)
        assert 'nonexistent_app' not in prefs.excluded_apps
        assert 'auth' in prefs.excluded_apps

    def test_post_redirects_to_diagram(self, staff_client):
        response = staff_client.post(EXCLUDED_APPS_URL, data={})
        assert response.status_code == 302
        assert response['Location'] == DIAGRAM_URL

    def test_post_with_no_selection_clears_excluded_apps(self, staff_client, staff_user):
        DiagramPreferences.objects.create(user=staff_user, excluded_apps=['auth'])
        staff_client.post(EXCLUDED_APPS_URL, data={})
        prefs = DiagramPreferences.objects.get(user=staff_user)
        assert prefs.excluded_apps == []
