"""Graph read service for retrieving vis-network subgraph data from Neo4j."""

import logging
from typing import Any

from neo4j import Session
from neo4j.exceptions import ServiceUnavailable

from app.core.database import get_driver

logger = logging.getLogger(__name__)

_SUBGRAPH_QUERY = """
CALL () {
    MATCH (n:Literature {project_id: $project_id, user_id: $user_id})
    RETURN collect({
        id: elementId(n),
        label: n.title,
        attributes: {
            hash: n.hash,
            background: n.text_background,
            methodology: n.text_methodology,
            gap: n.text_gap,
            objective: n.text_objective,
            futurework: n.text_futurework,
            storage_url: n.storage_url,
            storage_path: n.storage_path
        }
    }) AS nodes
}
CALL () {
    MATCH (source:Literature {project_id: $project_id, user_id: $user_id})-[r]->(target:Literature {project_id: $project_id, user_id: $user_id})
    RETURN collect({
        from: elementId(source),
        to: elementId(target),
        relation: type(r),
        label: coalesce(r.reasoning, null),
        fromTitle: source.title,
        toTitle: target.title,
        score: coalesce(r.score, null)
    }) AS edges
}
RETURN nodes, edges
"""


def _run_subgraph_query(
    session: Session,
    project_id: str,
    user_id: str,
) -> Any:
    """Execute the subgraph query and consume the single record immediately.

    Args:
        session: Active Neo4j session.
        project_id: The project identifier to scope the graph query.
        user_id: The user identifier to ensure data isolation.

    Returns:
        The single Neo4j record or None when the query is empty.
    """

    return session.run(
        _SUBGRAPH_QUERY,
        project_id=project_id,
        user_id=user_id,
    ).single()


def get_subgraph_by_workspace(
    session: Session,
    project_id: str,
    user_id: str,
) -> dict[str, list[dict[str, Any]]]:
    """Retrieve the full subgraph for a project in vis-network format.

    Executes a single-pass Cypher query with map projection to fetch
    all nodes and edges belonging to the specified project and user.

    Args:
        session: Active Neo4j session from dependency injection.
        project_id: The project identifier to scope the graph query.
        user_id: The user identifier to ensure data isolation.

    Returns:
        Dictionary with 'nodes' and 'edges' lists ready for vis-network.

    Raises:
        neo4j.exceptions.Neo4jError: When the Cypher query fails.
    """
    try:
        record = _run_subgraph_query(
            session,
            project_id=project_id,
            user_id=user_id,
        )
    except ServiceUnavailable as exc:
        logger.warning(
            "Neo4j connection defunct during subgraph read, opening a new session and retrying once",
            extra={"project_id": project_id, "error": str(exc)},
        )
        try:
            with get_driver().session() as retry_session:
                record = _run_subgraph_query(
                    retry_session,
                    project_id=project_id,
                    user_id=user_id,
                )
        except Exception as retry_exc:
            logger.error(
                "Neo4j subgraph read failed after retry with a new session",
                extra={"project_id": project_id, "error": str(retry_exc)},
            )
            raise

    if record is None:
        logger.info(
            "Empty subgraph returned",
            extra={"project_id": project_id},
        )
        return {"nodes": [], "edges": []}

    nodes = record["nodes"] or []
    edges = record["edges"] or []

    storage_url_present_count = 0
    storage_path_present_count = 0

    for node in nodes:
        attributes = node.get("attributes")
        if not isinstance(attributes, dict):
            attributes = {}

        attributes.setdefault("storage_url", None)
        attributes.setdefault("storage_path", None)
        node["attributes"] = attributes

        storage_url = attributes.get("storage_url")
        storage_path = attributes.get("storage_path")

        if storage_url not in (None, ""):
            storage_url_present_count += 1
        if storage_path not in (None, ""):
            storage_path_present_count += 1

    logger.info(
        "Subgraph retrieved",
        extra={
            "project_id": project_id,
            "node_count": len(nodes),
            "edge_count": len(edges),
            "storage_url_present_count": storage_url_present_count,
            "storage_path_present_count": storage_path_present_count,
        },
    )

    return {"nodes": nodes, "edges": edges}


def delete_literature_node_and_assets(
    session: Session,
    node_id: str,
    user_id: str,
) -> bool:
    """Delete a literature node from Neo4j and its physical assets from Supabase.

    Atomically removes:
    1. The node and all its relationships in Neo4j (DETACH DELETE).
    2. The physical PDF file in Supabase Storage.
    3. The vector chunks in Supabase Postgres.

    Args:
        session: Active Neo4j session.
        node_id: The elementId of the node to delete.
        user_id: The owner of the node (for authorization).

    Returns:
        bool: True if node was found and deleted, False otherwise.
    """
    # 1. Verify ownership and get metadata
    record = session.run(
        "MATCH (n:Literature) "
        "WHERE elementId(n) = $node_id AND n.user_id = $user_id "
        "RETURN n.hash AS file_hash, n.storage_path AS storage_path, n.project_id AS project_id",
        node_id=node_id,
        user_id=user_id,
    ).single()

    if not record:
        logger.warning(
            "Delete failed: Node not found or unauthorized",
            extra={"node_id": node_id, "user_id": user_id},
        )
        return False

    file_hash = record["file_hash"]
    storage_path = record["storage_path"]
    project_id = record["project_id"]

    # 2. Delete from Supabase (Best effort)
    from app.core.config import get_settings
    settings = get_settings()

    if file_hash or storage_path:
        from app.services.storage.supabase_client import delete_document_chunks_by_scope

        if file_hash and project_id:
            try:
                deleted_count = delete_document_chunks_by_scope(
                    file_hash=file_hash,
                    user_id=user_id,
                    project_id=project_id,
                )
                logger.info(
                    "Deleted document chunks",
                    extra={
                        "file_hash": file_hash,
                        "user_id": user_id,
                        "project_id": project_id,
                        "deleted_count": deleted_count,
                    },
                )
            except Exception as e:
                logger.error(
                    "Failed to delete chunks",
                    extra={
                        "error": str(e),
                        "file_hash": file_hash,
                        "user_id": user_id,
                        "project_id": project_id,
                    },
                )

        if storage_path:
            try:
                from supabase import create_client
                client = create_client(
                    settings.supabase_url,
                    settings.supabase_service_role_key,
                )
                bucket = client.storage.from_(settings.supabase_bucket)
                bucket.remove([storage_path])
                logger.info("Deleted file from storage", extra={"storage_path": storage_path})
            except Exception as e:
                logger.error("Failed to delete from storage", extra={"error": str(e), "storage_path": storage_path})

    # 3. DETACH DELETE in Neo4j
    session.run(
        "MATCH (n:Literature) WHERE elementId(n) = $node_id DETACH DELETE n",
        node_id=node_id,
    )

    logger.info(
        "Successfully deleted literature node",
        extra={"node_id": node_id, "user_id": user_id},
    )
    return True
