#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
Knowledge Graph Query Engine: SQLite-first search and path-finding.

Unified on SQLite as single source of truth (Phase 1 AGI refinement).
Falls back to JSON only if SQLite tables are empty.

Core functions:
  - query_causes(text)      Find what CAUSES or LEADS_TO the queried concept
  - query_prevents(text)    Find what PREVENTS the queried concept
  - query_related(text)     Find all nodes related to query (spreading activation)
  - query_chain(from, to)   Find causal path between two concepts
  - get_hub_nodes(n)        Get most connected knowledge (highest degree)
  - get_graph_stats()       Summary statistics about the graph
"""

import logging
from collections import defaultdict

from pulse.core.brain_utils import GRAPH_FILE, load_json_dict, search_relevance
from pulse.graph.graph_helpers import (  # noqa: F401
    _get_conn, _sqlite_has_data, _find_nodes_sql,
    _load_graph_json, _find_matching_nodes_json,
)

log = logging.getLogger("pulse.graph.query")


# === Public API ===

def query_causes(query: str, max_results: int = 5) -> list:
    """Find what CAUSES or LEADS_TO the queried concept."""
    conn = _get_conn()
    if _sqlite_has_data(conn):
        return _query_causes_sql(conn, query, max_results)
    return _query_causes_json(query, max_results)


def _query_causes_sql(conn, query: str, max_results: int) -> list:
    nodes = _find_nodes_sql(conn, query, limit=5)
    results = []
    for node in nodes:
        rows = conn.execute("""
            SELECT n.id, n.type, n.text, e.confidence, e.edge_type
            FROM graph_edges e
            JOIN graph_nodes n ON n.id = e.from_node
            WHERE e.to_node = ? AND e.edge_type IN ('CAUSES', 'LEADS_TO')
            ORDER BY e.confidence DESC
            LIMIT ?
        """, (node["id"], max_results)).fetchall()
        for r in rows:
            results.append({
                "text": r["text"] or r["id"],
                "type": r["type"] or "CONCEPT",
                "relationship": r["edge_type"],
                "confidence": r["confidence"] or 0.5,
                "source": "knowledge_graph",
            })
    return results[:max_results]


def _query_causes_json(query: str, max_results: int) -> list:
    graph = _load_graph_json()
    results = []
    matching_nodes = _find_matching_nodes_json(query, graph)
    for node_id in matching_nodes:
        for edge in graph.get("edges", []):
            if edge["to"] == node_id and edge["type"] in ("CAUSES", "LEADS_TO"):
                from_node = graph["nodes"].get(edge["from"], {})
                if from_node:
                    results.append({
                        "text": from_node.get("text", ""),
                        "type": from_node.get("type", "CONCEPT"),
                        "relationship": edge["type"],
                        "confidence": edge.get("confidence", 0.5),
                        "source": "knowledge_graph",
                    })
    return results[:max_results]


def query_prevents(query: str, max_results: int = 5) -> list:
    """Find what PREVENTS the queried concept."""
    conn = _get_conn()
    if _sqlite_has_data(conn):
        return _query_prevents_sql(conn, query, max_results)
    return _query_prevents_json(query, max_results)


def _query_prevents_sql(conn, query: str, max_results: int) -> list:
    nodes = _find_nodes_sql(conn, query, limit=5)
    results = []
    for node in nodes:
        rows = conn.execute("""
            SELECT n.id, n.type, n.text, e.confidence
            FROM graph_edges e
            JOIN graph_nodes n ON n.id = e.from_node
            WHERE e.to_node = ? AND e.edge_type = 'PREVENTS'
            ORDER BY e.confidence DESC
            LIMIT ?
        """, (node["id"], max_results)).fetchall()
        for r in rows:
            results.append({
                "text": r["text"] or r["id"],
                "type": r["type"] or "CONCEPT",
                "relationship": "PREVENTS",
                "confidence": r["confidence"] or 0.5,
                "source": "knowledge_graph",
            })
    return results[:max_results]


def _query_prevents_json(query: str, max_results: int) -> list:
    graph = _load_graph_json()
    results = []
    matching_nodes = _find_matching_nodes_json(query, graph)
    for node_id in matching_nodes:
        for edge in graph.get("edges", []):
            if edge["to"] == node_id and edge["type"] == "PREVENTS":
                from_node = graph["nodes"].get(edge["from"], {})
                if from_node:
                    results.append({
                        "text": from_node.get("text", ""),
                        "type": from_node.get("type", "CONCEPT"),
                        "relationship": "PREVENTS",
                        "confidence": edge.get("confidence", 0.5),
                        "source": "knowledge_graph",
                    })
    return results[:max_results]


def query_related(query: str, max_results: int = 5) -> list:
    """Find all nodes related to query using spreading activation (SQLite)."""
    try:
        from pulse.core.brain_db import get_conn
        from pulse.graph.activation import spreading_activation

        conn = get_conn()
        with conn:
            # Split multi-word queries into AND-chained LIKE
            words = [w for w in query.strip().split() if len(w) > 2]
            if not words:
                words = [query.strip()]
            text_conds = " AND ".join("text LIKE ?" for _ in words)
            id_conds = " AND ".join("id LIKE ?" for _ in words)
            params = [f"%{w}%" for w in words] * 2
            res = conn.execute(
                f"SELECT id FROM graph_nodes WHERE ({text_conds}) OR ({id_conds}) LIMIT 5",
                params
            ).fetchall()
            start_nodes = [r["id"] for r in res]

        if not start_nodes:
            res = conn.execute("SELECT id, text FROM graph_nodes LIMIT 1000").fetchall()
            from pulse.core.brain_utils import text_similarity
            matches = [(r["id"], text_similarity(query, r["text"])) for r in res]
            matches.sort(key=lambda x: x[1], reverse=True)
            start_nodes = [m[0] for m in matches if m[1] > 0.25][:5]

        if not start_nodes:
            return []

        activated = spreading_activation(
            start_nodes, decay=0.6, threshold=0.05, max_hops=3,
            max_nodes=max_results * 2, edge_types=["RELATED_TO", "COACTIVATION"]
        )
        results = []
        for n in activated[:max_results]:
            results.append({
                "text": n.get("text", n.get("id")),
                "type": n.get("type", "CONCEPT"),
                "relationship": "ACTIVATED",
                "confidence": n.get("max_activation", 0.5),
                "source": n.get("source", "brain"),
                "hop_distance": n.get("min_hops", 0),
            })
        return results
    except Exception as e:
        log.debug("Spreading activation failed: %s", e)
        return []



# Re-export extended functions so existing callers don't break
from pulse.graph.graph_query_ext import (  # noqa: F401
    query_chain,
    get_hub_nodes,
    get_graph_stats,
)
