"""Tests for DMS Phase 13 — Settings Architecture & Fixtures/Seeds.

Covers:
  * Settings registration — all DMS setting keys present in registry
  * Settings resolution — default values correct per spec
  * Settings API GET — returns all keys for tenant
  * Settings API PATCH — update a setting (requires dms.settings.manage)
  * Settings API PATCH — unknown key returns 400
  * Settings API PATCH — 401 unauthenticated
  * Settings API PATCH — 403 missing permission
  * Seed command — creates default document types for a tenant
  * Seed command — skips already-existing types (idempotent)
  * Seed command — dry-run does not persist
  * Seed command — seeds all 11 default document types
"""

from __future__ import annotations

import pytest

from simorgh.apps.dms.settings_api import PERM_SETTINGS_MANAGE, _DMS_SETTING_KEYS
from simorgh.apps.dms.seed_data import DEFAULT_DOCUMENT_TYPES


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def alice_membership(alice, tenant_acme, acme_tree, role_admin):
    """alice gets role_admin so she has dms.settings.manage."""
    from simorgh.apps.memberships.models import Membership

    m = Membership.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        role=role_admin,
    )
    m.users.add(alice)
    return m


@pytest.fixture
def bob(db):
    """User without any DMS permissions."""
    from django.contrib.auth import get_user_model

    User = get_user_model()
    return User.objects.create_user(
        "+989000000088",
        password="x",
        email="bob_settings@example.com",
    )


# ---------------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------------

def _url(path: str) -> str:
    return f"/api/v1/dms/{path}"


# ---------------------------------------------------------------------------
# Settings registry tests
# ---------------------------------------------------------------------------

def test_all_dms_setting_keys_registered(db):
    """All keys declared in _DMS_SETTING_KEYS must be in the registry."""
    from simorgh.apps.app_settings.registry import get_definition

    for key in _DMS_SETTING_KEYS:
        defn = get_definition(key)
        assert defn is not None, f"Setting {key!r} not registered"
        assert defn.key == key


def test_settings_defaults(db):
    """Default values match the spec in dms/settings.py."""
    from simorgh.apps.app_settings.registry import get_definition

    assert get_definition("dms.max_file_size_mb").default == 100
    assert get_definition("dms.ocr_enabled").default is False
    assert get_definition("dms.versioning_enabled").default is True
    assert get_definition("dms.retention_enabled").default is False
    assert get_definition("dms.watermark_enabled").default is False
    assert get_definition("dms.require_checkout_before_edit").default is False
    assert get_definition("dms.comments_enabled").default is True
    assert get_definition("dms.annotations_enabled").default is True
    assert get_definition("dms.preview_enabled").default is True
    assert get_definition("dms.default_view_mode").default == "list"
    assert isinstance(get_definition("dms.allowed_content_types").default, list)
    assert len(get_definition("dms.allowed_content_types").default) > 0


# ---------------------------------------------------------------------------
# Settings API — GET
# ---------------------------------------------------------------------------

def test_settings_api_get_returns_all_keys(client, alice, alice_membership, tenant_acme):
    """GET /dms/settings/ returns all DMS setting keys with default values."""
    client.force_login(alice)
    resp = client.get(_url("settings/"), HTTP_X_TENANT=tenant_acme.slug)
    assert resp.status_code == 200
    data = resp.json()
    for key in _DMS_SETTING_KEYS:
        assert key in data, f"Missing key {key!r} in response"


def test_settings_api_get_defaults(client, alice, alice_membership, tenant_acme):
    """Default values are returned when no overrides exist."""
    client.force_login(alice)
    resp = client.get(_url("settings/"), HTTP_X_TENANT=tenant_acme.slug)
    assert resp.status_code == 200
    data = resp.json()
    assert data["dms.max_file_size_mb"] == 100
    assert data["dms.ocr_enabled"] is False
    assert data["dms.versioning_enabled"] is True
    assert data["dms.default_view_mode"] == "list"


def test_settings_api_get_requires_auth(client, tenant_acme):
    """GET without authentication returns 401."""
    resp = client.get(_url("settings/"), HTTP_X_TENANT=tenant_acme.slug)
    assert resp.status_code == 401


# ---------------------------------------------------------------------------
# Settings API — PATCH
# ---------------------------------------------------------------------------

def test_settings_api_patch_updates_setting(client, alice, alice_membership, tenant_acme):
    """PATCH /dms/settings/ persists a new value for the tenant."""
    client.force_login(alice)
    resp = client.patch(
        _url("settings/"),
        data={"dms.max_file_size_mb": 250},
        content_type="application/json",
        HTTP_X_TENANT=tenant_acme.slug,
    )
    assert resp.status_code == 200
    data = resp.json()
    assert data["dms.max_file_size_mb"] == 250


def test_settings_api_patch_persists_across_requests(client, alice, alice_membership, tenant_acme):
    """Updated value is returned on subsequent GET."""
    client.force_login(alice)
    client.patch(
        _url("settings/"),
        data={"dms.ocr_enabled": True},
        content_type="application/json",
        HTTP_X_TENANT=tenant_acme.slug,
    )
    resp = client.get(_url("settings/"), HTTP_X_TENANT=tenant_acme.slug)
    assert resp.status_code == 200
    assert resp.json()["dms.ocr_enabled"] is True


def test_settings_api_patch_unknown_key_returns_400(client, alice, alice_membership, tenant_acme):
    """PATCH with an unknown key returns 400 with error details."""
    client.force_login(alice)
    resp = client.patch(
        _url("settings/"),
        data={"dms.nonexistent_setting": "value"},
        content_type="application/json",
        HTTP_X_TENANT=tenant_acme.slug,
    )
    assert resp.status_code == 400
    assert "errors" in resp.json()
    assert "dms.nonexistent_setting" in resp.json()["errors"]


def test_settings_api_patch_requires_auth(client, tenant_acme):
    """PATCH without authentication returns 401."""
    resp = client.patch(
        _url("settings/"),
        data={"dms.max_file_size_mb": 50},
        content_type="application/json",
        HTTP_X_TENANT=tenant_acme.slug,
    )
    assert resp.status_code == 401


def test_settings_api_patch_requires_permission(client, bob, tenant_acme, acme_tree):
    """PATCH without dms.settings.manage returns 403."""
    from simorgh.apps.iam.models import Role
    from simorgh.apps.memberships.models import Membership

    # bob has a membership but no permissions
    role = Role.objects.create(tenant=tenant_acme, code="noperm_role", name="No Perm")
    m = Membership.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        role=role,
    )
    m.users.add(bob)

    client.force_login(bob)
    resp = client.patch(
        _url("settings/"),
        data={"dms.max_file_size_mb": 50},
        content_type="application/json",
        HTTP_X_TENANT=tenant_acme.slug,
    )
    assert resp.status_code == 403


# ---------------------------------------------------------------------------
# Seed data tests
# ---------------------------------------------------------------------------

def test_seed_data_has_11_types():
    """DEFAULT_DOCUMENT_TYPES defines exactly 11 entries."""
    assert len(DEFAULT_DOCUMENT_TYPES) == 11


def test_seed_data_all_have_required_fields():
    """Every seed entry has code, name, icon, color, description."""
    required = {"code", "name", "icon", "color", "description"}
    for entry in DEFAULT_DOCUMENT_TYPES:
        missing = required - entry.keys()
        assert not missing, f"Entry {entry['code']!r} missing fields: {missing}"


def test_seed_data_codes_are_unique():
    """All seed codes are unique."""
    codes = [e["code"] for e in DEFAULT_DOCUMENT_TYPES]
    assert len(codes) == len(set(codes)), "Duplicate codes in DEFAULT_DOCUMENT_TYPES"


# ---------------------------------------------------------------------------
# Seed management command tests
# ---------------------------------------------------------------------------

def test_dms_seed_creates_default_document_types(tenant_acme, acme_tree, perms):
    """dms_seed command creates all 11 document types for a tenant."""
    from django.core.management import call_command
    from simorgh.apps.dms.documents.models import DocumentType

    assert DocumentType.objects.filter(tenant=tenant_acme).count() == 0

    call_command("dms_seed", tenant_slug=tenant_acme.slug, verbosity=0)

    types = DocumentType.objects.filter(tenant=tenant_acme, is_deleted=False)
    assert types.count() == len(DEFAULT_DOCUMENT_TYPES)


def test_dms_seed_is_idempotent(tenant_acme, acme_tree, perms):
    """Running dms_seed twice does not create duplicates."""
    from django.core.management import call_command
    from simorgh.apps.dms.documents.models import DocumentType

    call_command("dms_seed", tenant_slug=tenant_acme.slug, verbosity=0)
    call_command("dms_seed", tenant_slug=tenant_acme.slug, verbosity=0)

    types = DocumentType.objects.filter(tenant=tenant_acme, is_deleted=False)
    assert types.count() == len(DEFAULT_DOCUMENT_TYPES)


def test_dms_seed_dry_run_does_not_persist(tenant_acme, acme_tree, perms):
    """--dry-run flag does not write to the database."""
    from django.core.management import call_command
    from simorgh.apps.dms.documents.models import DocumentType

    call_command("dms_seed", tenant_slug=tenant_acme.slug, dry_run=True, verbosity=0)

    assert DocumentType.objects.filter(tenant=tenant_acme).count() == 0


def test_dms_seed_creates_correct_codes(tenant_acme, acme_tree, perms):
    """Created document types have the expected codes from seed_data."""
    from django.core.management import call_command
    from simorgh.apps.dms.documents.models import DocumentType

    call_command("dms_seed", tenant_slug=tenant_acme.slug, verbosity=0)

    expected_codes = {e["code"] for e in DEFAULT_DOCUMENT_TYPES}
    actual_codes = set(
        DocumentType.objects.filter(tenant=tenant_acme, is_deleted=False).values_list("code", flat=True)
    )
    assert actual_codes == expected_codes


def test_dms_seed_invalid_tenant_raises(perms):
    """dms_seed raises CommandError for unknown tenant slug."""
    from django.core.management import call_command
    from django.core.management.base import CommandError

    with pytest.raises(CommandError):
        call_command("dms_seed", tenant_slug="nonexistent-tenant-xyz", verbosity=0)


def test_dms_seed_skips_existing_types(tenant_acme, acme_tree, perms):
    """Types already existing are skipped (not duplicated or errored)."""
    from django.core.management import call_command
    from simorgh.apps.dms.documents.models import DocumentType

    # Pre-create one type
    DocumentType.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        code="contract",
        name="Existing Contract",
        is_active=True,
    )

    call_command("dms_seed", tenant_slug=tenant_acme.slug, verbosity=0)

    # Only one "contract" should exist
    assert DocumentType.objects.filter(tenant=tenant_acme, code="contract", is_deleted=False).count() == 1
    # Total is still 11 (pre-existing counted + 10 new)
    assert DocumentType.objects.filter(tenant=tenant_acme, is_deleted=False).count() == len(DEFAULT_DOCUMENT_TYPES)
