"""cortex/api/routes/memory/manage.py — CRUD write operations: delete, restore, patch."""

from __future__ import annotations

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

from fastapi import Depends, HTTPException, Request, status
from fastapi.responses import JSONResponse

from cortex.api.deps import PREFIX, _resolve_owner_id, _serialise, get_plugin
from cortex.api.models import (
    ForgetResponse,
    MemoryPatchRequest,
    MemoryPatchResponse,
)
from cortex.types import VALID_CATEGORIES

logger = logging.getLogger(__name__)


def _register(router, verify_api_key):
    """Register memory CRUD write endpoints (delete, restore, patch)."""

    @router.delete(
        f"{PREFIX}/memories/{{memory_id}}",
        response_model=ForgetResponse,
        summary="Delete a memory (soft or hard)",
        tags=["memories"],
        operation_id="delete_memory",
    )
    async def delete_memory(
        request: Request,
        memory_id: str,
        hard: bool = False,
        owner_id: Optional[str] = None,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> ForgetResponse:
        """
        Delete a memory.

        - Soft delete (default): marks status as 'archived'.
        - Hard delete (``?hard=true``): removes row from DB.
        - Logs action='deleted' to changelog regardless.
        """
        errors: List[str] = []
        storage = getattr(plugin, "storage", None)
        if storage is None:
            raise HTTPException(
                status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                detail="Storage not available",
            )

        effective_owner = _resolve_owner_id(request, owner_id)
        claim = await storage.get_claim(memory_id, owner_id=effective_owner)
        if claim is None:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Memory '{memory_id}' not found",
            )

        try:
            # Log changelog BEFORE hard delete (row will be gone after)
            existing_log = await storage.get_changelog(memory_id)
            next_version = (max((e.get("version", 0) for e in existing_log), default=0) + 1
                            if existing_log else 1)
            snapshot = f"{claim.subject} {claim.predicate} {claim.object_value}"
            action = "hard_deleted" if hard else "deleted"
            await storage.log_changelog(
                claim_id=memory_id,
                version=next_version,
                content_snapshot=snapshot,
                action=action,
                reason=f"Operator {'hard' if hard else 'soft'} delete via API",
                changed_by="operator",
            )
        except Exception as exc:
            errors.append(f"Changelog write failed: {exc}")

        try:
            await storage.delete_claim(memory_id, owner_id=effective_owner, hard=hard)
        except Exception as exc:
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="Internal server error",
            ) from exc

        return ForgetResponse(
            memory_id=memory_id,
            deleted=True,
            reason=f"Memory {'hard' if hard else 'soft'} deleted",
            errors=errors,
        )

    @router.post(
        f"{PREFIX}/memories/{{memory_id}}/restore",
        summary="Restore a soft-deleted memory",
        tags=["memories"],
        operation_id="restore_memory",
    )
    async def restore_memory(
        request: Request,
        memory_id: str,
        owner_id: Optional[str] = None,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> JSONResponse:
        """Restore a soft-deleted memory within the 90-day retention window.

        Clears ``is_deleted`` and ``deleted_at``, sets status back to 'active',
        and re-indexes for search.
        """
        storage = getattr(plugin, "storage", None)
        if storage is None:
            raise HTTPException(
                status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                detail="Storage not available",
            )

        effective_owner = _resolve_owner_id(request, owner_id)

        # Check if claim exists (including soft-deleted)
        claim = await storage.get_claim(memory_id, owner_id=effective_owner, include_deleted=True)
        if claim is None:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Memory '{memory_id}' not found",
            )

        # Check if it's actually soft-deleted
        is_deleted = getattr(claim, "is_deleted", 0) or 0
        if not is_deleted:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"Memory '{memory_id}' is not deleted — nothing to restore",
            )

        restored = await storage.restore_claim(memory_id, owner_id=effective_owner)
        if restored is None:
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="Restore failed",
            )

        # Log to changelog
        try:
            existing_log = await storage.get_changelog(memory_id)
            next_version = (max((e.get("version", 0) for e in existing_log), default=0) + 1
                            if existing_log else 1)
            snapshot = f"{restored.subject} {restored.predicate} {restored.object_value}"
            await storage.log_changelog(
                claim_id=memory_id,
                version=next_version,
                content_snapshot=snapshot,
                action="restored",
                reason="Memory restored from soft-delete via API",
                changed_by="operator",
            )
        except Exception:
            pass

        memory_dict = _serialise(restored.__dict__) if hasattr(restored, "__dict__") else {"id": memory_id}
        return JSONResponse(content={
            "memory_id": memory_id,
            "restored": True,
            "memory": memory_dict,
        })

    @router.patch(
        f"{PREFIX}/memories/{{memory_id}}",
        response_model=MemoryPatchResponse,
        summary="Update memory metadata",
        tags=["memories"],
        operation_id="patch_memory",
    )
    async def patch_memory(
        request: Request,
        memory_id: str,
        body: MemoryPatchRequest,
        owner_id: Optional[str] = None,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> MemoryPatchResponse:
        """Update metadata fields on a memory: category, sensitivity, status, confidence."""
        errors: List[str] = []
        storage = getattr(plugin, "storage", None)
        if storage is None:
            raise HTTPException(
                status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                detail="Storage not available",
            )

        effective_owner = _resolve_owner_id(request, owner_id)
        claim = await storage.get_claim(memory_id, owner_id=effective_owner)
        if claim is None:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Memory '{memory_id}' not found",
            )

        _valid_sensitivity = {"normal", "private", "secret"}
        _valid_status = {"active", "cooling", "deprecated", "archived", "superseded", "needs_review", "rejected"}
        _valid_categories = VALID_CATEGORIES

        updates: Dict[str, Any] = {}

        if body.category is not None:
            if body.category not in _valid_categories:
                raise HTTPException(
                    status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
                    detail=f"Invalid category '{body.category}'. Valid: {sorted(_valid_categories)}",
                )
            updates["category"] = body.category

        if body.sensitivity is not None:
            if body.sensitivity not in _valid_sensitivity:
                raise HTTPException(
                    status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
                    detail=f"Invalid sensitivity '{body.sensitivity}'. Valid: {sorted(_valid_sensitivity)}",
                )
            updates["sensitivity"] = body.sensitivity

        if body.status is not None:
            if body.status not in _valid_status:
                raise HTTPException(
                    status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
                    detail=f"Invalid status '{body.status}'. Valid: {sorted(_valid_status)}",
                )
            updates["status"] = body.status

        if body.confidence is not None:
            if not (0.0 <= body.confidence <= 1.0):
                raise HTTPException(
                    status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
                    detail="confidence must be between 0.0 and 1.0",
                )
            updates["confidence"] = body.confidence

        if not updates:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
                detail="No valid fields provided for update",
            )

        try:
            updated_claim = await storage.update_claim(
                memory_id,
                owner_id=effective_owner,
                **updates,
            )
        except Exception as exc:
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="Internal server error",
            ) from exc

        # Log to changelog
        try:
            existing_log = await storage.get_changelog(memory_id)
            next_version = (max((e.get("version", 0) for e in existing_log), default=0) + 1
                            if existing_log else 1)
            snapshot = f"{updated_claim.subject} {updated_claim.predicate} {updated_claim.object_value}"
            change_summary = ", ".join(f"{k}={v}" for k, v in updates.items())
            await storage.log_changelog(
                claim_id=memory_id,
                version=next_version,
                content_snapshot=snapshot,
                action="metadata_updated",
                reason=f"Operator patch: {change_summary}",
                changed_by="operator",
            )
        except Exception as exc:
            errors.append(f"Changelog write failed: {exc}")

        memory_dict = _serialise(updated_claim.__dict__) if hasattr(updated_claim, "__dict__") else {"id": memory_id}
        return MemoryPatchResponse(memory=memory_dict, errors=errors)
