"""
cortex/brain_graph/crud.py — High-level CRUD helpers for brain graph nodes and edges.

These helpers wrap the StorageAdapter to provide a clean interface for
upsert, read, update, and delete operations on brain_graph_index,
brain_graph_node, and brain_graph_edge tables.

All methods are async. Caller is responsible for lifecycle (initialize/close).
"""
from __future__ import annotations

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

from cortex.storage.adapter import StorageAdapter
from cortex.types import BrainGraphEdge, BrainGraphIndex, BrainGraphNode


# ---------------------------------------------------------------------------
# Graph-level CRUD
# ---------------------------------------------------------------------------


class BrainGraphCRUD:
    """
    High-level CRUD for brain graph indexes, nodes, and edges.

    Wraps the storage adapter to provide upsert semantics and
    convenience helpers used by BrainGraphBuilder.
    """

    def __init__(self, storage: StorageAdapter) -> None:
        self._storage = storage

    # ------------------------------------------------------------------ #
    # Index (graph) operations
    # ------------------------------------------------------------------ #

    async def create_graph(
        self,
        name: str,
        source_type: str,
        owner_id: str = "default",
        source_uri: Optional[str] = None,
        source_hash: Optional[str] = None,
        embedding_model_id: Optional[str] = None,
        builder_prompt_ver: Optional[str] = None,
        index_config: Optional[Dict[str, Any]] = None,
    ) -> BrainGraphIndex:
        """Create a new brain graph index record with status='building'."""
        return await self._storage.create_brain_graph(
            name=name,
            source_type=source_type,
            owner_id=owner_id,
            source_uri=source_uri,
            source_hash=source_hash,
            embedding_model_id=embedding_model_id,
            builder_prompt_ver=builder_prompt_ver,
            status="building",
            index_config=json.dumps(index_config) if index_config else None,
        )

    async def get_graph(self, graph_id: str) -> Optional[BrainGraphIndex]:
        """Return a single graph by ID, or None if not found."""
        return await self._storage.get_brain_graph(graph_id)

    async def find_by_hash(
        self, source_hash: str, owner_id: str = "default"
    ) -> Optional[BrainGraphIndex]:
        """
        Return an active graph with a matching SHA-256 source_hash.

        Used for dedup: if the source hasn't changed, skip re-indexing.
        Returns None if no matching active graph exists.
        """
        return await self._storage.find_brain_graph_by_hash(source_hash, owner_id)

    async def list_graphs(self, owner_id: str = "default") -> List[BrainGraphIndex]:
        """Return all graphs for an owner, newest first."""
        return await self._storage.list_brain_graphs(owner_id)

    async def activate_graph(
        self,
        graph_id: str,
        last_indexed: str,
        source_hash: Optional[str] = None,
        embedding_model_id: Optional[str] = None,
    ) -> BrainGraphIndex:
        """Mark a graph as active after a successful build."""
        kwargs: Dict[str, Any] = {"status": "active", "last_indexed": last_indexed}
        if source_hash:
            kwargs["source_hash"] = source_hash
        if embedding_model_id:
            kwargs["embedding_model_id"] = embedding_model_id
        return await self._storage.update_brain_graph(graph_id, **kwargs)

    async def update_graph(self, graph_id: str, **kwargs: Any) -> BrainGraphIndex:
        """Generic field update for a brain graph index record."""
        return await self._storage.update_brain_graph(graph_id, **kwargs)

    async def delete_graph(self, graph_id: str) -> None:
        """
        Delete a brain graph and cascade-delete all its nodes and edges.

        This is a hard delete. Embeddings stored in embedding_index
        are NOT removed here (handled separately if needed).
        """
        await self._storage.delete_brain_graph(graph_id)

    # ------------------------------------------------------------------ #
    # Node operations
    # ------------------------------------------------------------------ #

    async def upsert_node(
        self,
        graph_id: str,
        node_type: str,
        label: str,
        content: Optional[str] = None,
        compact_repr: Optional[str] = None,
        depth: int = 0,
        parent_id: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> BrainGraphNode:
        """
        Create a brain graph node.

        Returns the created BrainGraphNode. IDs are assigned by the DB.
        """
        return await self._storage.add_brain_graph_node(
            graph_id=graph_id,
            node_type=node_type,
            label=label,
            content=content,
            compact_repr=compact_repr,
            depth=depth,
            parent_id=parent_id,
            metadata_json=json.dumps(metadata) if metadata else None,
        )

    async def get_node(self, node_id: str) -> Optional[BrainGraphNode]:
        """Return a single node by ID."""
        return await self._storage.get_brain_graph_node(node_id)

    async def list_nodes(
        self, graph_id: str, node_type: Optional[str] = None
    ) -> List[BrainGraphNode]:
        """Return all nodes for a graph, optionally filtered by type."""
        return await self._storage.get_brain_graph_nodes(graph_id, node_type)

    async def update_node(self, node_id: str, **kwargs: Any) -> BrainGraphNode:
        """Update fields on a node (label, content, compact_repr, etc.)."""
        return await self._storage.update_brain_graph_node(node_id, **kwargs)

    async def delete_node(self, node_id: str) -> None:
        """
        Delete a node and any edges where it appears as source or target.
        """
        await self._storage.delete_brain_graph_node(node_id)

    # ------------------------------------------------------------------ #
    # Edge operations
    # ------------------------------------------------------------------ #

    async def upsert_edge(
        self,
        graph_id: str,
        source_id: str,
        target_id: str,
        edge_type: str,
        weight: float = 1.0,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> BrainGraphEdge:
        """
        Create a directed edge between two brain graph nodes.

        edge_type examples: RELATED_TO, DEPENDS_ON, IMPLEMENTS,
                            EXTENDS, CALLS, REFERENCES, etc.
        """
        return await self._storage.add_brain_graph_edge(
            graph_id=graph_id,
            source_id=source_id,
            target_id=target_id,
            edge_type=edge_type,
            weight=weight,
            metadata_json=json.dumps(metadata) if metadata else None,
        )

    async def get_edge(self, edge_id: str) -> Optional[BrainGraphEdge]:
        """Return a single edge by ID."""
        return await self._storage.get_brain_graph_edge(edge_id)

    async def list_edges(self, graph_id: str) -> List[BrainGraphEdge]:
        """Return all edges for a graph."""
        return await self._storage.get_brain_graph_edges(graph_id)

    async def update_edge(self, edge_id: str, **kwargs: Any) -> BrainGraphEdge:
        """Update fields on an edge (edge_type, weight, metadata_json)."""
        return await self._storage.update_brain_graph_edge(edge_id, **kwargs)

    async def delete_edge(self, edge_id: str) -> None:
        """Delete a single edge by ID."""
        await self._storage.delete_brain_graph_edge(edge_id)
