"""Tests for S2.2 Knowledge Base.

Covers:
- Model creation
- Service-layer: category CRUD, article CRUD, publish, archive, feedback
- Selector queries: visibility filtering, permission-aware search
- API endpoints: categories, articles, publish, archive, versions, feedback, public portal
- Permission registration
"""

from __future__ import annotations

import pytest

from simorgh.apps.accounts.models import User
from simorgh.apps.kb.models import (
    ArticleStatus,
    ArticleVisibility,
    KBArticle,
    KBArticleFeedback,
    KBArticleVersion,
    KBCategory,
)
from simorgh.apps.kb import services, selectors
from simorgh.apps.tenants.models import Tenant


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def tenant(db) -> Tenant:
    return Tenant.objects.create(slug="kb-test", name="KBTest")


@pytest.fixture
def user(db) -> User:
    return User.objects.create_user("+989400000001", password="x", email="kbuser@example.com", is_superuser=True)


@pytest.fixture
def user2(db) -> User:
    return User.objects.create_user("+989400000002", password="x", email="kbuser2@example.com")


@pytest.fixture
def category(tenant) -> KBCategory:
    return services.create_category(tenant_id=tenant.pk, name="General", slug="general")


@pytest.fixture
def article(tenant, user, category) -> KBArticle:
    return services.create_article(
        tenant_id=tenant.pk,
        title="Getting Started",
        slug="getting-started",
        category_id=category.pk,
        body_html="<p>Welcome</p>",
        author_id=user.pk,
    )


@pytest.fixture
def api_client():
    from rest_framework.test import APIClient
    return APIClient()


@pytest.fixture
def authed_client(api_client, user, tenant):
    api_client.force_login(user)
    return api_client, tenant.slug


# ---------------------------------------------------------------------------
# Model tests
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_category_create(tenant):
    cat = services.create_category(tenant_id=tenant.pk, name="FAQ", slug="faq")
    assert cat.pk is not None
    assert cat.name == "FAQ"
    assert cat.slug == "faq"
    assert cat.tenant_id == tenant.pk


@pytest.mark.django_db
def test_article_create_defaults(tenant, user, category):
    art = services.create_article(
        tenant_id=tenant.pk,
        title="My Article",
        slug="my-article",
        category_id=category.pk,
        author_id=user.pk,
    )
    assert art.status == ArticleStatus.DRAFT
    assert art.visibility == ArticleVisibility.INTERNAL
    assert art.published_at is None


@pytest.mark.django_db
def test_article_str(article):
    assert str(article) == "Getting Started"


# ---------------------------------------------------------------------------
# Service tests
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_publish_article(article, user):
    result = services.publish_article(article, changed_by_id=user.pk, change_summary="First publish")
    assert result.status == ArticleStatus.PUBLISHED
    assert result.published_at is not None
    # Version created
    assert KBArticleVersion.objects.filter(article=article, version=1).exists()


@pytest.mark.django_db
def test_publish_creates_version_snapshot(article, user):
    article.body_html = "<p>Updated body</p>"
    article.save()
    services.publish_article(article, changed_by_id=user.pk)
    version = KBArticleVersion.objects.get(article=article, version=1)
    assert version.body_html == "<p>Updated body</p>"
    assert version.changed_by_id == user.pk


@pytest.mark.django_db
def test_publish_increments_version_number(article, user):
    services.publish_article(article, changed_by_id=user.pk)
    article.body_html = "<p>V2</p>"
    article.save()
    services.publish_article(article, changed_by_id=user.pk, change_summary="Second publish")
    assert KBArticleVersion.objects.filter(article=article).count() == 2
    assert KBArticleVersion.objects.get(article=article, version=2).body_html == "<p>V2</p>"


@pytest.mark.django_db
def test_archive_article(article, user):
    services.publish_article(article, changed_by_id=user.pk)
    services.archive_article(article, changed_by_id=user.pk)
    article.refresh_from_db()
    assert article.status == ArticleStatus.ARCHIVED


@pytest.mark.django_db
def test_update_article(article):
    updated = services.update_article(article, title="Updated Title", body_html="<p>New</p>")
    assert updated.title == "Updated Title"
    assert updated.body_html == "<p>New</p>"


@pytest.mark.django_db
def test_delete_article_soft(article):
    services.delete_article(article)
    assert KBArticle.objects.filter(pk=article.pk).count() == 0
    # But still accessible via with_deleted
    assert KBArticle.objects.with_deleted().filter(pk=article.pk).count() == 1


@pytest.mark.django_db
def test_record_feedback_helpful(article, user):
    fb = services.record_feedback(article, user_id=user.pk, is_helpful=True, comment="Great!")
    assert fb.is_helpful is True
    assert fb.comment == "Great!"
    article.refresh_from_db()
    assert article.helpful_count == 1


@pytest.mark.django_db
def test_record_feedback_not_helpful(article):
    services.record_feedback(article, is_helpful=False)
    article.refresh_from_db()
    assert article.not_helpful_count == 1


@pytest.mark.django_db
def test_update_category(category):
    updated = services.update_category(category, name="Updated FAQ", slug="updated-faq")
    assert updated.name == "Updated FAQ"


@pytest.mark.django_db
def test_delete_category(category):
    pk = category.pk
    services.delete_category(category)
    assert not KBCategory.objects.filter(pk=pk).exists()


# ---------------------------------------------------------------------------
# Selector tests
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_selector_list_articles_for_tenant(tenant, user, category):
    art1 = services.create_article(tenant_id=tenant.pk, title="A1", slug="a1", category_id=category.pk, author_id=user.pk)
    art2 = services.create_article(tenant_id=tenant.pk, title="A2", slug="a2", category_id=category.pk, author_id=user.pk)
    tenant2 = Tenant.objects.create(slug="kb-other", name="Other")
    cat2 = services.create_category(tenant_id=tenant2.pk, name="Other", slug="other")
    services.create_article(tenant_id=tenant2.pk, title="Other", slug="other-art", category_id=cat2.pk, author_id=user.pk)
    arts = selectors.articles_for_tenant(tenant_id=tenant.pk)
    pks = {a.pk for a in arts}
    assert art1.pk in pks and art2.pk in pks


@pytest.mark.django_db
def test_selector_get_article_by_public_id(tenant, article):
    found = selectors.article_by_public_id(tenant.pk, str(article.public_id))
    assert found.pk == article.pk


@pytest.mark.django_db
def test_selector_get_article_by_public_id_not_found(tenant):
    import uuid
    with pytest.raises(KBArticle.DoesNotExist):
        selectors.article_by_public_id(tenant.pk, str(uuid.uuid4()))


@pytest.mark.django_db
def test_selector_list_categories(tenant):
    services.create_category(tenant_id=tenant.pk, name="Cat1", slug="cat1")
    services.create_category(tenant_id=tenant.pk, name="Cat2", slug="cat2")
    cats = selectors.categories_for_tenant(tenant_id=tenant.pk)
    assert cats.count() >= 2


# ---------------------------------------------------------------------------
# Permission registration
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_permissions_registered():
    from simorgh.apps.iam.registry import registered_permissions
    perms = {p.codename for p in registered_permissions()}
    assert "kb.article.view" in perms
    assert "kb.article.create" in perms
    assert "kb.article.publish" in perms
    assert "kb.category.manage" in perms


# ---------------------------------------------------------------------------
# API tests
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_api_list_categories(authed_client, category):
    client, slug = authed_client
    resp = client.get("/api/v1/kb/categories/", HTTP_X_TENANT=slug)
    assert resp.status_code == 200
    data = resp.json()
    assert any(c["name"] == "General" for c in data)


@pytest.mark.django_db
def test_api_create_category(authed_client, tenant):
    client, slug = authed_client
    resp = client.post(
        "/api/v1/kb/categories/",
        {"name": "Tutorials", "slug": "tutorials"},
        format="json",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 201
    assert resp.json()["name"] == "Tutorials"


@pytest.mark.django_db
def test_api_list_articles(authed_client, article):
    client, slug = authed_client
    resp = client.get("/api/v1/kb/articles/", HTTP_X_TENANT=slug)
    assert resp.status_code == 200
    data = resp.json()
    assert any(a["title"] == "Getting Started" for a in data)


@pytest.mark.django_db
def test_api_create_article(authed_client, category):
    client, slug = authed_client
    resp = client.post(
        "/api/v1/kb/articles/",
        {
            "title": "New Article",
            "slug": "new-article",
            "category_id": category.pk,
            "body_html": "<p>Content</p>",
        },
        format="json",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 201
    assert resp.json()["title"] == "New Article"


@pytest.mark.django_db
def test_api_get_article_detail(authed_client, article):
    client, slug = authed_client
    resp = client.get(f"/api/v1/kb/articles/{article.public_id}/", HTTP_X_TENANT=slug)
    assert resp.status_code == 200
    assert resp.json()["title"] == "Getting Started"


@pytest.mark.django_db
def test_api_update_article(authed_client, article):
    client, slug = authed_client
    resp = client.patch(
        f"/api/v1/kb/articles/{article.public_id}/",
        {"title": "Updated Title"},
        format="json",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 200
    assert resp.json()["title"] == "Updated Title"


@pytest.mark.django_db
def test_api_publish_article(authed_client, article):
    client, slug = authed_client
    resp = client.post(
        f"/api/v1/kb/articles/{article.public_id}/publish/",
        {},
        format="json",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 200
    assert resp.json()["status"] == "published"


@pytest.mark.django_db
def test_api_archive_article(authed_client, article, user):
    client, slug = authed_client
    services.publish_article(article, changed_by_id=user.pk)
    resp = client.post(
        f"/api/v1/kb/articles/{article.public_id}/archive/",
        {},
        format="json",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 200
    assert resp.json()["status"] == "archived"


@pytest.mark.django_db
def test_api_article_versions(authed_client, article, user):
    client, slug = authed_client
    services.publish_article(article, changed_by_id=user.pk, change_summary="v1")
    resp = client.get(f"/api/v1/kb/articles/{article.public_id}/versions/", HTTP_X_TENANT=slug)
    assert resp.status_code == 200
    versions = resp.json()
    assert len(versions) == 1
    assert versions[0]["version"] == 1


@pytest.mark.django_db
def test_api_article_feedback(authed_client, article, user):
    client, slug = authed_client
    services.publish_article(article, changed_by_id=user.pk)
    resp = client.post(
        f"/api/v1/kb/articles/{article.public_id}/feedback/",
        {"is_helpful": True},
        format="json",
        HTTP_X_TENANT=slug,
    )
    # feedback endpoint returns 200 with counts
    assert resp.status_code == 200
    assert "helpful_count" in resp.json()


@pytest.mark.django_db
def test_api_article_not_found(authed_client):
    import uuid
    client, slug = authed_client
    resp = client.get(f"/api/v1/kb/articles/{uuid.uuid4()}/", HTTP_X_TENANT=slug)
    assert resp.status_code == 404


@pytest.mark.django_db
def test_api_public_article(authed_client, article, user):
    client, slug = authed_client
    # Must be published + public visibility
    services.update_article(article, visibility=ArticleVisibility.PUBLIC)
    services.publish_article(article, changed_by_id=user.pk)
    resp = client.get(f"/api/v1/kb/public/getting-started/", HTTP_X_TENANT=slug)
    assert resp.status_code == 200
    assert resp.json()["title"] == "Getting Started"


@pytest.mark.django_db
def test_api_unauthenticated_401(api_client, tenant):
    resp = api_client.get("/api/v1/kb/articles/", HTTP_X_TENANT=tenant.slug)
    assert resp.status_code == 401


@pytest.mark.django_db
def test_api_delete_article(authed_client, article):
    client, slug = authed_client
    resp = client.delete(f"/api/v1/kb/articles/{article.public_id}/", HTTP_X_TENANT=slug)
    assert resp.status_code == 204
    assert not KBArticle.objects.filter(pk=article.pk).exists()


@pytest.mark.django_db
def test_api_search_articles(authed_client, article, user):
    client, slug = authed_client
    services.publish_article(article, changed_by_id=user.pk)
    resp = client.get("/api/v1/kb/articles/search/?q=getting", HTTP_X_TENANT=slug)
    assert resp.status_code == 200
    results = resp.json()
    assert any("Getting" in r.get("title", "") for r in results)
