"""
neo4j.py
--------
Neo4j implementation of GraphBackend.
"""

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

try:
    from neo4j import GraphDatabase
except ImportError:
    GraphDatabase = None

from hieromem_core.backends.interface import GraphBackend


class Neo4jBackend(GraphBackend):
    def __init__(self, uri: str, user: str, password: str):
        if not GraphDatabase:
            raise ImportError("neo4j driver is required for Neo4jBackend")
        self.driver = GraphDatabase.driver(uri, auth=(user, password))

    def close(self):
        self.driver.close()

    def add_triples(self, triples: List[Tuple[str, str, str, Dict[str, Any]]]) -> None:
        with self.driver.session() as session:
            for u, rel, v, props in triples:
                # Basic MERGE implementation
                # Note: Relation needs to be sanitized? Neo4j rel types are strict?
                # Using dynamic query construction warning! for MVP assume safe rels.
                cypher = (
                    f"MERGE (a:Node {{id: $u}}) "
                    f"MERGE (b:Node {{id: $v}}) "
                    f"MERGE (a)-[r:`{rel}`]->(b) "
                    f"SET r += $props"
                )
                session.run(cypher, u=u, v=v, props=props)

    def search_triples(
        self,
        head: Optional[str] = None,
        relation: Optional[str] = None,
        tail: Optional[str] = None,
    ) -> List[Tuple[str, str, str, Dict[str, Any]]]:

        match_clause = "MATCH (a:Node)-[r]->(b:Node)"
        where_clauses = []
        params = {}

        if head:
            where_clauses.append("a.id = $head")
            params["head"] = head
        if tail:
            where_clauses.append("b.id = $tail")
            params["tail"] = tail
        if relation:
            # Relation types in match
            match_clause = f"MATCH (a:Node)-[r:`{relation}`]->(b:Node)"

        query = match_clause
        if where_clauses:
            query += " WHERE " + " AND ".join(where_clauses)

        query += " RETURN a.id, type(r), b.id, properties(r)"

        with self.driver.session() as session:
            result = session.run(query, **params)
            return [
                (r["a.id"], r["type(r)"], r["b.id"], r["properties(r)"]) for r in result
            ]

    def get_neighbors(
        self, node: str, depth: int = 1
    ) -> List[Tuple[str, str, str, Dict[str, Any]]]:
        # Only depth 1 supported in interface broadly, but Cypher supports variable length path
        query = (
            "MATCH (a:Node {id: $node})-[r]->(b:Node) "
            "RETURN a.id, type(r), b.id, properties(r)"
        )
        with self.driver.session() as session:
            result = session.run(query, node=node)
            return [
                (r["a.id"], r["type(r)"], r["b.id"], r["properties(r)"]) for r in result
            ]

    def delete_triples(self, triples: List[Tuple[str, str, str]]) -> None:
        with self.driver.session() as session:
            for u, r, v in triples:
                query = (
                    f"MATCH (a:Node {{id: $u}})-[r:`{r}`]->(b:Node {{id: $v}}) "
                    "DELETE r"
                )
                session.run(query, u=u, v=v)

    def clear(self) -> None:
        with self.driver.session() as session:
            session.run("MATCH (n) DETACH DELETE n")
