"""cortex/api/routes/entities.py — Entity + GDPR endpoints.

Canonical GDPR owner-forget path: ``DELETE /api/v1/owners/{owner_id}/forget``
Deprecated path: ``DELETE /api/v1/owner/{owner_id}/forget`` (sunset 2026-06-01, issue #301)
"""

from __future__ import annotations

import logging
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Depends, HTTPException, Response, status
from fastapi.responses import JSONResponse

from cortex.api.deps import PREFIX, _serialise, get_plugin
from cortex.api.deprecation import add_deprecation_headers
from cortex.api.models import EntityListResponse, MergeEntityRequest

logger = logging.getLogger(__name__)

router = APIRouter()


def _make_routes(verify_api_key):
    """Register entity routes with the given auth dependency."""

    @router.get(
        f"{PREFIX}/entities",
        response_model=EntityListResponse,
        summary="Browse entities",
        tags=["entities"],
    )
    async def list_entities(
        entity_type: Optional[str] = None,
        sort_by: str = "last_seen",
        page: int = 1,
        per_page: int = 50,
        limit: int = 50,
        offset: int = 0,
        search: Optional[str] = None,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> EntityListResponse:
        """Browse resolved entities with optional type filter, sort, and pagination."""
        errors: List[str] = []
        entities: List[Dict[str, Any]] = []
        total = 0

        if plugin.storage is None:
            errors.append("Storage not available")
            return EntityListResponse(errors=errors)

        eff_limit = per_page if per_page != 50 else limit
        eff_offset = (page - 1) * per_page if page > 1 else offset

        try:
            if hasattr(plugin.storage, "list_entities"):
                raw = await plugin.storage.list_entities(
                    owner_id=getattr(plugin.config, "owner_id", "default"),
                    entity_type=entity_type,
                    sort_by=sort_by,
                    limit=eff_limit,
                    offset=eff_offset,
                    search=search,
                )
                if hasattr(plugin.storage, "count_entities"):
                    total = await plugin.storage.count_entities(
                        owner_id=getattr(plugin.config, "owner_id", "default"),
                    )
                else:
                    total = len(raw)

                for ent in raw:
                    d = _serialise(ent.__dict__) if hasattr(ent, "__dict__") else _serialise(ent)
                    if hasattr(plugin.storage, "get_entity_claim_count"):
                        try:
                            d["claim_count"] = await plugin.storage.get_entity_claim_count(d.get("id", ""))
                        except Exception:
                            d["claim_count"] = 0
                    entities.append(d)
            else:
                errors.append("Entity listing not supported by storage")
        except Exception as exc:
            err = f"Entity list failed: {exc}"
            logger.error(err, exc_info=True)
            errors.append(err)

        return EntityListResponse(entities=entities, total=total, errors=errors)

    @router.get(
        f"{PREFIX}/entities/{{entity_id}}",
        summary="Entity detail",
        tags=["entities"],
    )
    async def get_entity_detail(
        entity_id: str,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> JSONResponse:
        """Return entity info + all aliases + claims + top relationships."""
        storage = getattr(plugin, "storage", None)
        if storage is None:
            raise HTTPException(503, detail="Storage not available")
        try:
            entity = await storage.get_entity(entity_id)
            if entity is None:
                raise HTTPException(404, detail=f"Entity {entity_id} not found")
            data = _serialise(entity.__dict__) if hasattr(entity, "__dict__") else _serialise(entity)

            aliases: List[Dict[str, Any]] = []
            if hasattr(storage, "get_entity_aliases"):
                raw_aliases = await storage.get_entity_aliases(entity_id)
                aliases = [
                    _serialise(a.__dict__) if hasattr(a, "__dict__") else _serialise(a)
                    for a in raw_aliases
                ]
            data["aliases"] = aliases

            claims: List[Dict[str, Any]] = []
            effective_owner = getattr(plugin.config, "owner_id", "default")
            if hasattr(storage, "get_claims_by_entity"):
                claim_objs = await storage.get_claims_by_entity(
                    entity_id,
                    owner_id=effective_owner,
                )
                claims = [
                    _serialise(c.__dict__) if hasattr(c, "__dict__") else _serialise(c)
                    for c in claim_objs
                ]
            data["claims"] = claims

            claim_count = len(claims) if claims else 0
            if not claims and hasattr(storage, "get_entity_claim_count"):
                claim_count = await storage.get_entity_claim_count(entity_id)
            data["claim_count"] = claim_count

            relationships: List[Dict[str, Any]] = []
            if hasattr(storage, "get_entity_relationships"):
                relationships = await storage.get_entity_relationships(entity_id, limit=20)
            data["relationships"] = relationships

            return JSONResponse(content={"entity": data})
        except HTTPException:
            raise
        except Exception as exc:
            logger.error("get_entity_detail failed: %s", exc, exc_info=True)
            raise HTTPException(500, detail="Internal server error")

    @router.get(
        f"{PREFIX}/entities/{{entity_id}}/claims",
        summary="Claims referencing entity",
        tags=["entities"],
    )
    async def get_entity_claims_endpoint(
        entity_id: str,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> JSONResponse:
        """Return all claims where this entity is subject or object."""
        storage = getattr(plugin, "storage", None)
        if storage is None:
            raise HTTPException(503, detail="Storage not available")
        try:
            entity = await storage.get_entity(entity_id)
            if entity is None:
                raise HTTPException(404, detail=f"Entity {entity_id} not found")
            if not hasattr(storage, "get_claims_by_entity"):
                return JSONResponse(content={"claims": [], "total": 0})
            effective_owner = getattr(plugin.config, "owner_id", "default")
            claims = await storage.get_claims_by_entity(
                entity_id,
                owner_id=effective_owner,
            )
            items = [
                _serialise(c.__dict__) if hasattr(c, "__dict__") else _serialise(c)
                for c in claims
            ]
            return JSONResponse(content={"claims": items, "total": len(items)})
        except HTTPException:
            raise
        except Exception as exc:
            logger.error("get_entity_claims failed: %s", exc, exc_info=True)
            raise HTTPException(500, detail="Internal server error")

    @router.post(
        f"{PREFIX}/entities/{{entity_id}}/merge",
        summary="Merge entity with another",
        tags=["entities"],
    )
    async def merge_entity(
        entity_id: str,
        body: MergeEntityRequest,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> JSONResponse:
        """Merge entity_id INTO target_entity_id (keep target, dissolve source)."""
        storage = getattr(plugin, "storage", None)
        if storage is None:
            raise HTTPException(503, detail="Storage not available")
        try:
            src = await storage.get_entity(entity_id)
            if src is None:
                raise HTTPException(404, detail=f"Source entity {entity_id} not found")
            tgt = await storage.get_entity(body.target_entity_id)
            if tgt is None:
                raise HTTPException(404, detail=f"Target entity {body.target_entity_id} not found")
            merged = await storage.merge_entities(
                keep_id=body.target_entity_id,
                merge_id=entity_id,
            )
            data = _serialise(merged.__dict__) if hasattr(merged, "__dict__") else _serialise(merged)
            return JSONResponse(
                content={
                    "merged_entity": data,
                    "dissolved_id": entity_id,
                    "kept_id": body.target_entity_id,
                }
            )
        except HTTPException:
            raise
        except Exception as exc:
            logger.error("merge_entity failed: %s", exc, exc_info=True)
            raise HTTPException(500, detail="Internal server error")

    # ---- GDPR forget endpoints (#108) ----

    async def gdpr_forget_entity(
        entity_id: str,
        request: Any,
        owner_id: Optional[str] = None,
        dry_run: bool = False,
    ) -> dict:
        """DELETE /api/v1/entities/{entity_id}/forget"""
        from cortex.tools.gdpr import forget_entity as _forget_entity

        if not owner_id:
            raise HTTPException(status_code=400, detail="owner_id query parameter is required")

        plugin = get_plugin()
        storage = plugin.storage
        emitter = getattr(plugin, "emitter", None)

        try:
            result = await _forget_entity(
                storage,
                entity_id=entity_id,
                owner_id=owner_id,
                dry_run=dry_run,
                emitter=emitter,
            )
        except PermissionError as exc:
            raise HTTPException(status_code=403, detail="Internal server error")
        except Exception as exc:
            raise HTTPException(status_code=500, detail=f"Forget failed: {exc}")

        return result.to_dict()

    async def gdpr_forget_owner(
        owner_id: str,
        request: Any,
        dry_run: bool = False,
    ) -> dict:
        """DELETE /api/v1/owner/{owner_id}/forget"""
        from cortex.tools.gdpr import forget_owner as _forget_owner

        confirm = request.headers.get("X-Confirm-Delete", "").lower()
        if confirm != "true":
            raise HTTPException(
                status_code=400,
                detail="Header X-Confirm-Delete: true is required for owner forget",
            )

        plugin = get_plugin()
        storage = plugin.storage
        emitter = getattr(plugin, "emitter", None)

        try:
            result = await _forget_owner(
                storage,
                owner_id=owner_id,
                dry_run=dry_run,
                emitter=emitter,
            )
        except Exception as exc:
            raise HTTPException(status_code=500, detail=f"Owner forget failed: {exc}")

        return result.to_dict()

    router.add_api_route(
        f"{PREFIX}/entities/{{entity_id}}/forget",
        methods=["DELETE"],
        endpoint=gdpr_forget_entity,
        summary="GDPR forget — hard-delete one entity and all its claims",
        tags=["gdpr"],
    )

    # ---- CANONICAL: /owners/{owner_id}/forget  (plural — issue #301) ----
    router.add_api_route(
        f"{PREFIX}/owners/{{owner_id}}/forget",
        methods=["DELETE"],
        endpoint=gdpr_forget_owner,
        summary="GDPR forget — delete ALL data for an owner (requires X-Confirm-Delete: true)",
        tags=["gdpr"],
    )

    # ---- DEPRECATED: /owner/{owner_id}/forget  (singular — issue #301) ----
    async def gdpr_forget_owner_deprecated(
        owner_id: str,
        request: Any,
        response: Response,
        dry_run: bool = False,
    ) -> dict:
        """DEPRECATED — use ``DELETE /api/v1/owners/{owner_id}/forget``.

        .. deprecated:: sunset 2026-06-01
        """
        add_deprecation_headers(response, f"{PREFIX}/owners/{owner_id}/forget")
        return await gdpr_forget_owner(owner_id=owner_id, request=request, dry_run=dry_run)

    router.add_api_route(
        f"{PREFIX}/owner/{{owner_id}}/forget",
        methods=["DELETE"],
        endpoint=gdpr_forget_owner_deprecated,
        summary="GDPR forget owner — DEPRECATED, use /owners/{owner_id}/forget",
        tags=["gdpr (deprecated)"],
        deprecated=True,
    )
