"""Tests for DMS Phase 16 — Performance: Caching + Background Jobs.

Covers:
  * cache.py — key builder formats
  * cache.py — invalidation helpers
  * api/pagination.py — DocumentPagination defaults
  * documents/views.py — detail GET populates cache, second hit served from cache
  * documents/views.py — PATCH / DELETE invalidate cache
  * search/views.py — GET populates cache
  * repositories/views.py — folder list GET populates cache
  * repositories/views.py — folder create / rename / delete / move invalidate cache
  * tasks.py — dms_rebuild_search_index task importable
"""

from __future__ import annotations

import pytest
from django.core.cache import cache

from simorgh.apps.dms.cache import (
    DOCUMENT_DETAIL_TTL,
    FOLDER_TREE_TTL,
    SEARCH_TTL,
    document_detail_key,
    folder_children_key,
    folder_tree_key,
    invalidate_document,
    invalidate_folder_children,
    invalidate_repo_folders,
    search_results_key,
)
from simorgh.apps.dms.documents.permissions import (
    PERM_DOCUMENT_CREATE,
    PERM_DOCUMENT_MANAGE,
    PERM_DOCUMENT_PUBLISH,
    PERM_DOCUMENT_VIEW,
)
from simorgh.apps.dms.repositories.permissions import (
    PERM_FOLDER_MANAGE,
    PERM_FOLDER_VIEW,
)
from simorgh.apps.dms.search.iam_permissions import PERM_SEARCH_QUERY
from simorgh.core.context import RequestContext, use_request_context


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

ALL_PERMS = frozenset(
    {
        PERM_DOCUMENT_VIEW,
        PERM_DOCUMENT_CREATE,
        PERM_DOCUMENT_MANAGE,
        PERM_DOCUMENT_PUBLISH,
        PERM_FOLDER_VIEW,
        PERM_FOLDER_MANAGE,
        PERM_SEARCH_QUERY,
    }
)


@pytest.fixture(autouse=True)
def clear_cache_before():
    cache.clear()
    yield
    cache.clear()


@pytest.fixture
def ctx(alice, tenant_acme, acme_tree):
    return RequestContext(
        actor=alice,
        tenant=tenant_acme,
        org_node_ids=frozenset({acme_tree["root"].pk}),
        permissions=ALL_PERMS,
    )


@pytest.fixture
def alice_membership(alice, tenant_acme, acme_tree, role_admin):
    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 repo(tenant_acme, acme_tree):
    from simorgh.apps.dms.repositories.models import Repository

    return Repository.objects.create(
        name="Phase 16 Repo",
        slug="ph16-repo",
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
    )


@pytest.fixture
def doc(repo, tenant_acme, acme_tree):
    from simorgh.apps.dms.documents.models import Document

    return Document.objects.create(
        title="Phase 16 Document",
        repository=repo,
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
    )


@pytest.fixture
def folder(repo, tenant_acme, acme_tree):
    from simorgh.apps.dms.repositories.models import Folder

    return Folder.objects.create(
        name="Root Folder",
        repository=repo,
        parent=None,
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
    )


# ---------------------------------------------------------------------------
# cache.py — key format tests
# ---------------------------------------------------------------------------


def test_document_detail_key_format():
    key = document_detail_key(1, "abc")
    assert key == "dms:doc:1:abc"


def test_search_results_key_format():
    key = search_results_key(1, {"q": "hello"})
    assert key.startswith("dms:search:1:")


def test_folder_tree_key_format():
    key = folder_tree_key(1, "repoid")
    assert key == "dms:folders:1:repoid:root"


def test_folder_children_key_format():
    key = folder_children_key(1, "folderid")
    assert key == "dms:folders:1:folderid:children"


def test_document_detail_ttl():
    assert DOCUMENT_DETAIL_TTL == 300


def test_search_ttl():
    assert SEARCH_TTL == 60


def test_folder_tree_ttl():
    assert FOLDER_TREE_TTL == 600


# ---------------------------------------------------------------------------
# cache.py — invalidation helpers
# ---------------------------------------------------------------------------


def test_invalidate_document_clears_cache():
    key = document_detail_key(1, "xyz")
    cache.set(key, {"title": "test"}, timeout=300)
    assert cache.get(key) is not None
    invalidate_document(1, "xyz")
    assert cache.get(key) is None


def test_invalidate_repo_folders_clears_cache():
    key = folder_tree_key(1, "repoid")
    cache.set(key, [{"id": 1}], timeout=600)
    assert cache.get(key) is not None
    invalidate_repo_folders(1, "repoid")
    assert cache.get(key) is None


def test_invalidate_folder_children_clears_cache():
    key = folder_children_key(1, "folderid")
    cache.set(key, [{"id": 2}], timeout=600)
    assert cache.get(key) is not None
    invalidate_folder_children(1, "folderid")
    assert cache.get(key) is None


# ---------------------------------------------------------------------------
# api/pagination.py
# ---------------------------------------------------------------------------


def test_document_pagination_page_size():
    from simorgh.apps.dms.api.pagination import DocumentPagination

    p = DocumentPagination()
    assert p.page_size == 25
    assert p.max_page_size == 200
    assert p.page_size_query_param == "page_size"


# ---------------------------------------------------------------------------
# documents/views.py — cache integration
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_document_detail_api_populates_cache(client, alice, tenant_acme, acme_tree, alice_membership, doc, ctx):
    client.force_login(alice)
    with use_request_context(ctx):
        resp = client.get(
            f"/api/v1/dms/documents/{doc.public_id}/",
            HTTP_X_TENANT=tenant_acme.slug,
        )
    assert resp.status_code == 200
    cache_key = document_detail_key(tenant_acme.pk, str(doc.public_id))
    assert cache.get(cache_key) is not None


@pytest.mark.django_db
def test_document_detail_api_returns_from_cache(client, alice, tenant_acme, acme_tree, alice_membership, doc, ctx):
    """Second GET must be served from cache (inject sentinel to verify)."""
    cache_key = document_detail_key(tenant_acme.pk, str(doc.public_id))
    sentinel = {"title": "CACHED_SENTINEL", "id": str(doc.public_id)}
    cache.set(cache_key, sentinel, timeout=300)

    client.force_login(alice)
    with use_request_context(ctx):
        resp = client.get(
            f"/api/v1/dms/documents/{doc.public_id}/",
            HTTP_X_TENANT=tenant_acme.slug,
        )
    assert resp.status_code == 200
    assert resp.json()["title"] == "CACHED_SENTINEL"


@pytest.mark.django_db
def test_document_update_invalidates_cache(client, alice, tenant_acme, acme_tree, alice_membership, doc, ctx):
    cache_key = document_detail_key(tenant_acme.pk, str(doc.public_id))
    cache.set(cache_key, {"title": "stale"}, timeout=300)

    client.force_login(alice)
    with use_request_context(ctx):
        resp = client.patch(
            f"/api/v1/dms/documents/{doc.public_id}/",
            data={"title": "Updated Title"},
            content_type="application/json",
            HTTP_X_TENANT=tenant_acme.slug,
        )
    assert resp.status_code == 200
    assert cache.get(cache_key) is None


@pytest.mark.django_db
def test_document_delete_invalidates_cache(client, alice, tenant_acme, acme_tree, alice_membership, doc, ctx):
    cache_key = document_detail_key(tenant_acme.pk, str(doc.public_id))
    cache.set(cache_key, {"title": "stale"}, timeout=300)

    client.force_login(alice)
    with use_request_context(ctx):
        resp = client.delete(
            f"/api/v1/dms/documents/{doc.public_id}/",
            HTTP_X_TENANT=tenant_acme.slug,
        )
    assert resp.status_code == 204
    assert cache.get(cache_key) is None


# ---------------------------------------------------------------------------
# search/views.py — cache integration
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_search_api_populates_cache(client, alice, tenant_acme, acme_tree, alice_membership, doc, ctx):
    client.force_login(alice)
    with use_request_context(ctx):
        resp = client.get(
            "/api/v1/dms/search/",
            HTTP_X_TENANT=tenant_acme.slug,
        )
    assert resp.status_code == 200
    cache_key = search_results_key(tenant_acme.pk, {})
    assert cache.get(cache_key) is not None


@pytest.mark.django_db
def test_search_api_returns_from_cache(client, alice, tenant_acme, acme_tree, alice_membership, ctx):
    """Inject sentinel into search cache and verify response is served from it."""
    cache_key = search_results_key(tenant_acme.pk, {})
    sentinel = [{"title": "SEARCH_SENTINEL"}]
    cache.set(cache_key, sentinel, timeout=60)

    client.force_login(alice)
    with use_request_context(ctx):
        resp = client.get(
            "/api/v1/dms/search/",
            HTTP_X_TENANT=tenant_acme.slug,
        )
    assert resp.status_code == 200
    assert resp.json() == sentinel


# ---------------------------------------------------------------------------
# repositories/views.py — cache integration
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_folder_list_api_populates_cache(client, alice, tenant_acme, acme_tree, alice_membership, repo, ctx):
    client.force_login(alice)
    with use_request_context(ctx):
        resp = client.get(
            f"/api/v1/dms/repositories/{repo.public_id}/folders/",
            HTTP_X_TENANT=tenant_acme.slug,
        )
    assert resp.status_code == 200
    cache_key = folder_tree_key(tenant_acme.pk, str(repo.public_id))
    assert cache.get(cache_key) is not None


@pytest.mark.django_db
def test_folder_create_invalidates_folder_cache(client, alice, tenant_acme, acme_tree, alice_membership, repo, ctx):
    cache_key = folder_tree_key(tenant_acme.pk, str(repo.public_id))
    cache.set(cache_key, [{"name": "stale"}], timeout=600)

    client.force_login(alice)
    with use_request_context(ctx):
        resp = client.post(
            f"/api/v1/dms/repositories/{repo.public_id}/folders/",
            data={"name": "New Folder"},
            content_type="application/json",
            HTTP_X_TENANT=tenant_acme.slug,
        )
    assert resp.status_code == 201
    assert cache.get(cache_key) is None


@pytest.mark.django_db
def test_folder_rename_invalidates_cache(client, alice, tenant_acme, acme_tree, alice_membership, repo, folder, ctx):
    cache_key = folder_tree_key(tenant_acme.pk, str(repo.public_id))
    cache.set(cache_key, [{"name": "stale"}], timeout=600)

    client.force_login(alice)
    with use_request_context(ctx):
        resp = client.patch(
            f"/api/v1/dms/repositories/{repo.public_id}/folders/{folder.public_id}/",
            data={"name": "Renamed Folder"},
            content_type="application/json",
            HTTP_X_TENANT=tenant_acme.slug,
        )
    assert resp.status_code == 200
    assert cache.get(cache_key) is None


@pytest.mark.django_db
def test_folder_delete_invalidates_cache(client, alice, tenant_acme, acme_tree, alice_membership, repo, folder, ctx):
    cache_key = folder_tree_key(tenant_acme.pk, str(repo.public_id))
    cache.set(cache_key, [{"name": "stale"}], timeout=600)

    client.force_login(alice)
    with use_request_context(ctx):
        resp = client.delete(
            f"/api/v1/dms/repositories/{repo.public_id}/folders/{folder.public_id}/",
            HTTP_X_TENANT=tenant_acme.slug,
        )
    assert resp.status_code == 204
    assert cache.get(cache_key) is None


@pytest.mark.django_db
def test_folder_children_api_populates_cache(client, alice, tenant_acme, acme_tree, alice_membership, repo, folder, ctx):
    client.force_login(alice)
    with use_request_context(ctx):
        resp = client.get(
            f"/api/v1/dms/repositories/{repo.public_id}/folders/{folder.public_id}/children/",
            HTTP_X_TENANT=tenant_acme.slug,
        )
    assert resp.status_code == 200
    cache_key = folder_children_key(tenant_acme.pk, str(folder.public_id))
    assert cache.get(cache_key) is not None


# ---------------------------------------------------------------------------
# tasks.py — smoke test
# ---------------------------------------------------------------------------


def test_dms_rebuild_search_index_task_smoke():
    from simorgh.apps.dms.tasks import dms_rebuild_search_index

    assert callable(dms_rebuild_search_index)
