"""Catalog Engine API views.

URL layout (all under /api/v1/catalog/):

  Categories
    GET  /categories/              — list categories
    POST /categories/              — create category  (catalog.category.manage)
    GET  /categories/<pk>/         — retrieve
    PATCH /categories/<pk>/        — update           (catalog.category.manage)
    DELETE /categories/<pk>/       — delete           (catalog.category.manage)
    GET  /categories/tree/         — nested tree

  Items
    GET  /items/                   — list (filterable)
    POST /items/                   — create draft     (catalog.item.create)
    GET  /items/search/            — full-text search
    GET  /items/<public_id>/       — retrieve         (catalog.item.view)
    PATCH /items/<public_id>/      — update           (catalog.item.update)
    DELETE /items/<public_id>/     — soft-delete      (catalog.item.delete)
    POST /items/<public_id>/submit/   — submit for review (catalog.item.publish)
    POST /items/<public_id>/publish/  — publish        (catalog.item.publish)
    POST /items/<public_id>/retire/   — retire         (catalog.item.publish)
    POST /items/<public_id>/revert/   — revert to draft (catalog.item.publish)
    GET  /items/<public_id>/versions/ — version list
    GET  /items/<public_id>/related/  — related items
"""

from __future__ import annotations

from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response

from simorgh.apps.catalog import selectors as sel
from simorgh.apps.catalog import services
from simorgh.apps.catalog.models import CatalogCategory, CatalogItem, CatalogItemStatus, CatalogItemVersion

PERM_CATEGORY_MANAGE = "catalog.category.manage"
PERM_ITEM_VIEW       = "catalog.item.view"
PERM_ITEM_CREATE     = "catalog.item.create"
PERM_ITEM_UPDATE     = "catalog.item.update"
PERM_ITEM_PUBLISH    = "catalog.item.publish"
PERM_ITEM_DELETE     = "catalog.item.delete"


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 _get_org_node_id(request: Request, tenant_id: int) -> int:
    try:
        from simorgh.apps.organizations.models import OrganizationNode
        node = OrganizationNode.objects.filter(tenant_id=tenant_id).order_by("lft").first()
        return node.pk if node else 0
    except Exception:
        return 0


def _require_perm(request: Request, codename: str) -> None:
    from rest_framework.exceptions import PermissionDenied

    from simorgh.core.context import current_request_context
    ctx = current_request_context()
    if ctx.is_superuser:
        return
    if codename not in (ctx.permissions or set()):
        raise PermissionDenied(f"Permission required: {codename}")


def _get_actor_id(request: Request) -> int | None:
    from simorgh.core.context import current_request_context
    ctx = current_request_context()
    return ctx.actor.pk if ctx.actor else None


def _serialize_category(cat: CatalogCategory) -> dict:
    return {
        "id": cat.pk,
        "public_id": str(cat.public_id),
        "name": cat.name,
        "slug": cat.slug,
        "catalog_type": cat.catalog_type,
        "parent_id": cat.parent_id,
        "parent_name": cat.parent.name if cat.parent else None,
        "description": cat.description,
        "icon": cat.icon,
        "sort_order": cat.sort_order,
        "is_active": cat.is_active,
        "created_at": cat.created_at.isoformat(),
    }


def _serialize_item(item: CatalogItem, *, detail: bool = False) -> dict:
    data = {
        "id": item.pk,
        "public_id": str(item.public_id),
        "name": item.name,
        "slug": item.slug,
        "catalog_type": item.catalog_type,
        "catalog_type_display": item.get_catalog_type_display() if hasattr(item, "get_catalog_type_display") else item.catalog_type,
        "category_id": item.category_id,
        "category_name": item.category.name if item.category else None,
        "short_description": item.short_description,
        "status": item.status,
        "status_display": item.get_status_display() if hasattr(item, "get_status_display") else item.status,
        "owner_id": item.owner_id,
        "owner_name": item.owner.get_full_name() or item.owner.email if item.owner_id else None,
        "published_at": item.published_at.isoformat() if item.published_at else None,
        "retired_at": item.retired_at.isoformat() if item.retired_at else None,
        "image_id": item.image_id,
        "related_items": item.related_items,
        "documentation_content_id": str(item.documentation_content_id) if item.documentation_content_id else None,
        "created_at": item.created_at.isoformat(),
        "updated_at": item.updated_at.isoformat(),
    }
    if detail:
        data.update({
            "description": item.description,
            "ai_description": item.ai_description,
            "ai_semantic_type": item.ai_semantic_type,
            "version_count": item.versions.count(),
        })
    return data


def _serialize_version(ver: CatalogItemVersion) -> dict:
    return {
        "id": ver.pk,
        "public_id": str(ver.public_id),
        "catalog_item_id": ver.catalog_item_id,
        "version": ver.version,
        "name": ver.name,
        "description": ver.description,
        "status": ver.status,
        "changed_by_id": ver.changed_by_id,
        "changed_by_name": ver.changed_by.get_full_name() or ver.changed_by.email if ver.changed_by_id else None,
        "change_summary": ver.change_summary,
        "created_at": ver.created_at.isoformat(),
    }


def _int_param(request: Request, key: str) -> int | None:
    val = request.GET.get(key)
    if val is not None:
        try:
            return int(val)
        except (ValueError, TypeError):
            return None


# ── Categories ──────────────────────────────────────────────────────────────

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def category_list(request: Request) -> Response:
    tid = _get_tenant_id(request)
    if not tid:
        return Response([], status=status.HTTP_200_OK)
    cats = sel.categories_for_tenant(tid)
    return Response([_serialize_category(c) for c in cats])


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def category_tree_view(request: Request) -> Response:
    tid = _get_tenant_id(request)
    if not tid:
        return Response([], status=status.HTTP_200_OK)
    tree = sel.category_tree(tid)
    return Response(tree)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def category_create(request: Request) -> Response:
    tid = _require_tenant(request)
    _require_perm(request, PERM_CATEGORY_MANAGE)
    org_id = _get_org_node_id(request, tid)

    data = request.data
    cat = services.create_category(
        tenant_id=tid,
        organization_node_id=org_id,
        name=data["name"],
        slug=data["slug"],
        catalog_type=data.get("catalog_type", "service"),
        parent_id=data.get("parent_id"),
        description=data.get("description", ""),
        icon=data.get("icon", ""),
        sort_order=data.get("sort_order", 0),
    )
    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:
    tid = _require_tenant(request)

    try:
        cat = sel.category_by_pk(tid, pk)
    except CatalogCategory.DoesNotExist:
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)

    if request.method == "GET":
        return Response(_serialize_category(cat))

    _require_perm(request, PERM_CATEGORY_MANAGE)

    if request.method == "DELETE":
        services.delete_category(cat)
        return Response(status=status.HTTP_204_NO_CONTENT)

    data = request.data
    cat = services.update_category(
        cat,
        name=data.get("name"),
        slug=data.get("slug"),
        catalog_type=data.get("catalog_type"),
        parent_id=data.get("parent_id", ...),
        description=data.get("description"),
        icon=data.get("icon"),
        sort_order=data.get("sort_order"),
        is_active=data.get("is_active"),
    )
    return Response(_serialize_category(cat))


# ── Catalog Items ───────────────────────────────────────────────────────────

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def item_list(request: Request) -> Response:
    tid = _get_tenant_id(request)
    if not tid:
        return Response([], status=status.HTTP_200_OK)

    _require_perm(request, PERM_ITEM_VIEW)

    items = sel.items_for_tenant(
        tid,
        catalog_type=request.GET.get("catalog_type"),
        status=request.GET.get("status"),
        category_id=_int_param(request, "category_id"),
        owner_id=_int_param(request, "owner_id"),
    )
    return Response([_serialize_item(item) for item in items[:100]])


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def item_search(request: Request) -> Response:
    tid = _get_tenant_id(request)
    if not tid:
        return Response([], status=status.HTTP_200_OK)

    q = request.GET.get("q", "").strip()
    if len(q) < 2:
        return Response([], status=status.HTTP_200_OK)

    items = sel.search_items(tid, q)
    return Response([_serialize_item(item) for item in items])


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def item_create(request: Request) -> Response:
    tid = _require_tenant(request)
    _require_perm(request, PERM_ITEM_CREATE)
    org_id = _get_org_node_id(request, tid)

    data = request.data
    item = services.create_catalog_item(
        tenant_id=tid,
        organization_node_id=org_id,
        name=data["name"],
        slug=data["slug"],
        catalog_type=data.get("catalog_type", "product"),
        owner_id=data.get("owner_id") or _get_actor_id(request),
        category_id=data.get("category_id"),
        description=data.get("description", ""),
        short_description=data.get("short_description", ""),
        image_id=data.get("image_id"),
        documentation_content_id=data.get("documentation_content_id"),
    )
    return Response(_serialize_item(item, detail=True), status=status.HTTP_201_CREATED)


@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def item_detail(request: Request, public_id: str) -> Response:
    tid = _require_tenant(request)

    try:
        item = sel.item_by_public_id(tid, public_id)
    except CatalogItem.DoesNotExist:
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)

    if request.method == "GET":
        _require_perm(request, PERM_ITEM_VIEW)
        return Response(_serialize_item(item, detail=True))

    if request.method == "DELETE":
        _require_perm(request, PERM_ITEM_DELETE)
        services.delete_catalog_item(item)
        return Response(status=status.HTTP_204_NO_CONTENT)

    _require_perm(request, PERM_ITEM_UPDATE)
    data = request.data

    item = services.update_catalog_item(
        item,
        name=data.get("name"),
        slug=data.get("slug"),
        catalog_type=data.get("catalog_type"),
        category_id=data.get("category_id", ...),
        description=data.get("description"),
        short_description=data.get("short_description"),
        owner_id=data.get("owner_id", ...),
        image_id=data.get("image_id", ...),
        related_items=data.get("related_items"),
        documentation_content_id=data.get("documentation_content_id", ...),
        ai_description=data.get("ai_description"),
        ai_semantic_type=data.get("ai_semantic_type"),
    )
    return Response(_serialize_item(item, detail=True))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def item_submit(request: Request, public_id: str) -> Response:
    tid = _require_tenant(request)
    _require_perm(request, PERM_ITEM_PUBLISH)
    actor_id = _get_actor_id(request)

    try:
        item = sel.item_by_public_id(tid, public_id)
    except CatalogItem.DoesNotExist:
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)

    try:
        item = services.submit_for_review(item, actor_id=actor_id or 0)
    except ValueError as e:
        return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)

    return Response(_serialize_item(item, detail=True))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def item_publish(request: Request, public_id: str) -> Response:
    tid = _require_tenant(request)
    _require_perm(request, PERM_ITEM_PUBLISH)
    actor_id = _get_actor_id(request)

    try:
        item = sel.item_by_public_id(tid, public_id)
    except CatalogItem.DoesNotExist:
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)

    try:
        item = services.publish_item(
            item,
            actor_id=actor_id or 0,
            change_summary=request.data.get("change_summary", ""),
        )
    except ValueError as e:
        return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)

    return Response(_serialize_item(item, detail=True))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def item_retire(request: Request, public_id: str) -> Response:
    tid = _require_tenant(request)
    _require_perm(request, PERM_ITEM_PUBLISH)
    actor_id = _get_actor_id(request)

    try:
        item = sel.item_by_public_id(tid, public_id)
    except CatalogItem.DoesNotExist:
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)

    try:
        item = services.retire_item(item, actor_id=actor_id or 0)
    except ValueError as e:
        return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)

    return Response(_serialize_item(item, detail=True))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def item_revert(request: Request, public_id: str) -> Response:
    tid = _require_tenant(request)
    _require_perm(request, PERM_ITEM_PUBLISH)
    actor_id = _get_actor_id(request)

    try:
        item = sel.item_by_public_id(tid, public_id)
    except CatalogItem.DoesNotExist:
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)

    try:
        item = services.revert_to_draft(item, actor_id=actor_id or 0)
    except ValueError as e:
        return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)

    return Response(_serialize_item(item, detail=True))


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def item_versions(request: Request, public_id: str) -> Response:
    tid = _require_tenant(request)

    try:
        item = sel.item_by_public_id(tid, public_id)
    except CatalogItem.DoesNotExist:
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)

    versions = sel.versions_for_item(item)
    return Response([_serialize_version(v) for v in versions])


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def item_related(request: Request, public_id: str) -> Response:
    tid = _require_tenant(request)
    _require_perm(request, PERM_ITEM_VIEW)

    try:
        item = sel.item_by_public_id(tid, public_id)
    except CatalogItem.DoesNotExist:
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)

    if not item.related_items:
        return Response([], status=status.HTTP_200_OK)

    related = CatalogItem.objects.filter(
        tenant_id=tid,
        public_id__in=item.related_items,
        status=CatalogItemStatus.PUBLISHED,
    ).select_related("category")[:20]

    return Response([_serialize_item(r) for r in related])
