"""KB API views.

URL layout (all under /kb/):

  Categories
    GET  /categories/              — list tenant categories
    POST /categories/              — create category  (kb.category.manage)
    GET  /categories/<id>/         — retrieve
    PATCH /categories/<id>/        — update           (kb.category.manage)
    DELETE /categories/<id>/       — delete           (kb.category.manage)

  Articles
    GET  /articles/                — list (admin view, all statuses)
    POST /articles/                — create           (kb.article.create)
    GET  /articles/search/         — full-text search (authenticated)
    GET  /articles/<public_id>/    — retrieve         (kb.article.view)
    PATCH /articles/<public_id>/   — update           (kb.article.update)
    DELETE /articles/<public_id>/  — soft-delete      (kb.article.delete)
    POST /articles/<public_id>/publish/  — publish    (kb.article.publish)
    POST /articles/<public_id>/archive/  — archive    (kb.article.publish)
    GET  /articles/<public_id>/versions/ — version list
    POST /articles/<public_id>/feedback/ — submit feedback (AllowAny)

  Public portal (no auth required for public articles)
    GET  /public/<slug>/           — public article by slug
"""

from __future__ import annotations

from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response

from simorgh.apps.kb import selectors as sel
from simorgh.apps.kb import services
from simorgh.apps.kb.models import KBArticle, KBArticleVersion, KBCategory


# ---------------------------------------------------------------------------
# Permission constants
# ---------------------------------------------------------------------------

PERM_CATEGORY_MANAGE = "kb.category.manage"
PERM_ARTICLE_VIEW = "kb.article.view"
PERM_ARTICLE_CREATE = "kb.article.create"
PERM_ARTICLE_UPDATE = "kb.article.update"
PERM_ARTICLE_PUBLISH = "kb.article.publish"
PERM_ARTICLE_DELETE = "kb.article.delete"


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _get_tenant_id(request: Request) -> int | None:
    tenant = getattr(request, "tenant", None)
    return tenant.pk if tenant else None


def _require_tenant(request: Request) -> int:
    tid = _get_tenant_id(request)
    if not tid:
        from rest_framework.exceptions import PermissionDenied
        raise PermissionDenied("Tenant header required.")
    return tid


def _require_perm(request: Request, codename: str) -> None:
    from simorgh.core.context import current_request_context
    from rest_framework.exceptions import PermissionDenied
    ctx = current_request_context()
    if ctx.is_superuser:
        return
    if codename not in (ctx.permissions or set()):
        raise PermissionDenied(f"Permission required: {codename}")


def _serialize_category(cat: KBCategory) -> dict:
    return {
        "id": cat.pk,
        "public_id": str(cat.public_id),
        "name": cat.name,
        "slug": cat.slug,
        "parent_id": cat.parent_id,
        "parent_name": cat.parent.name if cat.parent else None,
        "icon": cat.icon,
        "order": cat.order,
        "created_at": cat.created_at.isoformat(),
    }


def _serialize_article(article: KBArticle, *, detail: bool = False) -> dict:
    data: dict = {
        "id": article.pk,
        "public_id": str(article.public_id),
        "title": article.title,
        "slug": article.slug,
        "status": article.status,
        "visibility": article.visibility,
        "category_id": article.category_id,
        "category_name": article.category.name if article.category_id else None,
        "author_id": article.author_id,
        "author_name": getattr(article, "author", None) and (
            getattr(article.author, "get_full_name", lambda: "")() or str(article.author)
        ),
        "view_count": article.view_count,
        "helpful_count": article.helpful_count,
        "not_helpful_count": article.not_helpful_count,
        "published_at": article.published_at.isoformat() if article.published_at else None,
        "created_at": article.created_at.isoformat(),
        "updated_at": article.updated_at.isoformat(),
    }
    if detail:
        data["body_html"] = article.body_html
    return data


def _serialize_version(v: KBArticleVersion) -> dict:
    return {
        "id": v.pk,
        "version": v.version,
        "changed_by_id": v.changed_by_id,
        "changed_by_name": getattr(v, "changed_by", None) and (
            getattr(v.changed_by, "get_full_name", lambda: "")() or str(v.changed_by)
        ),
        "change_summary": v.change_summary,
        "created_at": v.created_at.isoformat(),
    }


def _get_article_or_404(tenant_id: int, public_id: str) -> KBArticle:
    try:
        return sel.article_by_public_id(tenant_id, public_id)
    except KBArticle.DoesNotExist:
        from rest_framework.exceptions import NotFound
        raise NotFound("Article not found.")


def _get_category_or_404(tenant_id: int, pk: int) -> KBCategory:
    try:
        return sel.category_by_id(tenant_id, pk)
    except KBCategory.DoesNotExist:
        from rest_framework.exceptions import NotFound
        raise NotFound("Category not found.")


# ---------------------------------------------------------------------------
# Categories
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def category_list_create(request: Request) -> Response:
    """GET/POST /kb/categories/"""
    tenant_id = _require_tenant(request)

    if request.method == "GET":
        qs = sel.categories_for_tenant(tenant_id)
        return Response([_serialize_category(c) for c in qs])

    _require_perm(request, PERM_CATEGORY_MANAGE)
    data = request.data
    try:
        cat = services.create_category(
            tenant_id=tenant_id,
            name=data["name"],
            slug=data["slug"],
            parent_id=data.get("parent_id"),
            icon=data.get("icon", ""),
            order=int(data.get("order", 0)),
        )
    except KeyError as exc:
        return Response({"detail": f"Missing field: {exc}"}, status=status.HTTP_400_BAD_REQUEST)
    return Response(_serialize_category(cat), status=status.HTTP_201_CREATED)


@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def category_detail(request: Request, pk: int) -> Response:
    """GET/PATCH/DELETE /kb/categories/<pk>/"""
    tenant_id = _require_tenant(request)
    cat = _get_category_or_404(tenant_id, pk)

    if request.method == "GET":
        return Response(_serialize_category(cat))

    _require_perm(request, PERM_CATEGORY_MANAGE)

    if request.method == "PATCH":
        data = request.data
        cat = services.update_category(
            cat,
            name=data.get("name"),
            slug=data.get("slug"),
            parent_id=data.get("parent_id"),
            icon=data.get("icon"),
            order=data.get("order"),
        )
        return Response(_serialize_category(cat))

    # DELETE
    services.delete_category(cat)
    return Response(status=status.HTTP_204_NO_CONTENT)


# ---------------------------------------------------------------------------
# Articles
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def article_list_create(request: Request) -> Response:
    """GET/POST /kb/articles/"""
    tenant_id = _require_tenant(request)

    if request.method == "GET":
        _require_perm(request, PERM_ARTICLE_VIEW)
        qs = sel.articles_for_tenant(
            tenant_id,
            status=request.query_params.get("status"),
            visibility=request.query_params.get("visibility"),
            category_id=request.query_params.get("category_id"),
            author_id=request.query_params.get("author_id"),
        )
        return Response([_serialize_article(a) for a in qs])

    _require_perm(request, PERM_ARTICLE_CREATE)
    data = request.data
    try:
        article = services.create_article(
            tenant_id=tenant_id,
            title=data["title"],
            slug=data["slug"],
            category_id=int(data["category_id"]),
            body_html=data.get("body_html", ""),
            author_id=int(data.get("author_id", request.user.pk)),
            visibility=data.get("visibility", "internal"),
        )
    except KeyError as exc:
        return Response({"detail": f"Missing field: {exc}"}, status=status.HTTP_400_BAD_REQUEST)

    article.refresh_from_db()
    article = sel.article_by_public_id(tenant_id, str(article.public_id))
    return Response(_serialize_article(article, detail=True), status=status.HTTP_201_CREATED)


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def article_search(request: Request) -> Response:
    """GET /kb/articles/search/?q="""
    tenant_id = _require_tenant(request)
    q = request.query_params.get("q", "").strip()
    if not q:
        return Response([])
    # Use selector for visibility-aware results
    user_id = request.user.pk
    qs = sel.articles_visible_to(tenant_id, user_id=user_id).filter(title__icontains=q)[:30]
    return Response([_serialize_article(a) for a in qs])


@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def article_detail(request: Request, public_id: str) -> Response:
    """GET/PATCH/DELETE /kb/articles/<public_id>/"""
    tenant_id = _require_tenant(request)
    article = _get_article_or_404(tenant_id, public_id)

    if request.method == "GET":
        _require_perm(request, PERM_ARTICLE_VIEW)
        services.increment_view_count(article)
        return Response(_serialize_article(article, detail=True))

    if request.method == "PATCH":
        _require_perm(request, PERM_ARTICLE_UPDATE)
        data = request.data
        article = services.update_article(
            article,
            title=data.get("title"),
            slug=data.get("slug"),
            body_html=data.get("body_html"),
            category_id=int(data["category_id"]) if "category_id" in data else None,
            visibility=data.get("visibility"),
        )
        return Response(_serialize_article(article, detail=True))

    # DELETE
    _require_perm(request, PERM_ARTICLE_DELETE)
    services.delete_article(article)
    return Response(status=status.HTTP_204_NO_CONTENT)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def article_publish(request: Request, public_id: str) -> Response:
    """POST /kb/articles/<public_id>/publish/"""
    tenant_id = _require_tenant(request)
    _require_perm(request, PERM_ARTICLE_PUBLISH)
    article = _get_article_or_404(tenant_id, public_id)
    article = services.publish_article(
        article,
        changed_by_id=request.user.pk,
        change_summary=request.data.get("change_summary", ""),
    )
    return Response(_serialize_article(article, detail=True))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def article_archive(request: Request, public_id: str) -> Response:
    """POST /kb/articles/<public_id>/archive/"""
    tenant_id = _require_tenant(request)
    _require_perm(request, PERM_ARTICLE_PUBLISH)
    article = _get_article_or_404(tenant_id, public_id)
    article = services.archive_article(article, changed_by_id=request.user.pk)
    return Response(_serialize_article(article, detail=True))


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def article_versions(request: Request, public_id: str) -> Response:
    """GET /kb/articles/<public_id>/versions/"""
    tenant_id = _require_tenant(request)
    _require_perm(request, PERM_ARTICLE_VIEW)
    article = _get_article_or_404(tenant_id, public_id)
    versions = article.versions.select_related("changed_by").order_by("-version")
    return Response([_serialize_version(v) for v in versions])


@api_view(["POST"])
@permission_classes([AllowAny])
def article_feedback(request: Request, public_id: str) -> Response:
    """POST /kb/articles/<public_id>/feedback/  — open to anonymous users."""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)
    try:
        article = sel.article_by_public_id(tenant_id, public_id)
    except KBArticle.DoesNotExist:
        return Response({"detail": "Article not found."}, status=status.HTTP_404_NOT_FOUND)

    data = request.data
    is_helpful_raw = data.get("is_helpful")
    if is_helpful_raw is None:
        return Response({"detail": "is_helpful is required."}, status=status.HTTP_400_BAD_REQUEST)

    user_id = request.user.pk if request.user.is_authenticated else None
    services.record_feedback(
        article,
        user_id=user_id,
        is_helpful=bool(is_helpful_raw),
        comment=data.get("comment", ""),
    )
    return Response(
        {
            "helpful_count": article.helpful_count,
            "not_helpful_count": article.not_helpful_count,
        }
    )


# ---------------------------------------------------------------------------
# Public portal (unauthenticated access)
# ---------------------------------------------------------------------------

@api_view(["GET"])
@permission_classes([AllowAny])
def article_by_slug_view(request: Request, slug: str) -> Response:
    """GET /kb/public/<slug>/  — returns only published + public articles."""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)
    try:
        article = sel.article_by_slug(tenant_id, slug)
    except KBArticle.DoesNotExist:
        return Response({"detail": "Article not found."}, status=status.HTTP_404_NOT_FOUND)

    services.increment_view_count(article)
    return Response(_serialize_article(article, detail=True))
