"""Reusable Cypher query functions for decision traces and precedent search."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from koan.graph.client import GraphClient, GraphRecord


async def get_run_trace(
    client: GraphClient,
    run_id: str,
) -> list[GraphRecord]:
    """Retrieve the complete decision trace for a run.

    Returns decisions with their actions, policies, entities, and precedents,
    ordered by timestamp.
    """
    return await client.execute(
        """
        MATCH (r:Run {run_id: $run_id})-[:CONTAINS]->(d:Decision)
        OPTIONAL MATCH (d)-[:LED_TO]->(a:Action)
        OPTIONAL MATCH (d)-[:GOVERNED_BY]->(p:Policy)
        OPTIONAL MATCH (d)-[:REFERENCED]->(e:Entity)
        OPTIONAL MATCH (d)-[:CITED_PRECEDENT]->(prev:Decision)
        RETURN d.decision_id AS decision_id,
               d.description AS description,
               d.decision_type AS decision_type,
               d.confidence AS confidence,
               d.reasoning AS reasoning,
               d.timestamp AS timestamp,
               a.action_id AS action_id,
               a.type AS action_type,
               a.tool_name AS tool_name,
               a.error AS action_error,
               p.policy_id AS policy_id,
               p.name AS policy_name,
               e.entity_id AS entity_id,
               e.entity_type AS entity_type,
               prev.decision_id AS precedent_id
        ORDER BY d.timestamp
        """,
        {"run_id": run_id},
    )


async def find_precedents(
    client: GraphClient,
    *,
    entity_id: str,
    decision_type: str,
    before: str | None = None,
    limit: int = 5,
) -> list[GraphRecord]:
    """Find past decisions of a given type involving the same entity.

    Args:
        entity_id: The entity to search precedents for.
        decision_type: Filter to this decision type.
        before: ISO timestamp — only return decisions before this time.
        limit: Max results to return.
    """
    params: dict[str, Any] = {
        "entity_id": entity_id,
        "decision_type": decision_type,
        "limit": limit,
    }

    time_filter = ""
    if before:
        time_filter = "AND d.timestamp < $before"
        params["before"] = before

    return await client.execute(
        f"""
        MATCH (d:Decision)-[:REFERENCED]->(e:Entity {{entity_id: $entity_id}})
        WHERE d.decision_type = $decision_type
          {time_filter}
        OPTIONAL MATCH (d)<-[:CONTAINS]-(r:Run)-[:PRODUCED]->(o:Outcome)
        RETURN d.decision_id AS decision_id,
               d.description AS description,
               d.reasoning AS reasoning,
               d.confidence AS confidence,
               d.timestamp AS timestamp,
               o.status AS outcome_status,
               o.evaluation_score AS evaluation_score
        ORDER BY d.timestamp DESC
        LIMIT $limit
        """,
        params,
    )


async def find_exceptions(
    client: GraphClient,
    policy_id: str,
    *,
    limit: int = 10,
) -> list[GraphRecord]:
    """Find past exceptions granted for a given policy."""
    return await client.execute(
        """
        MATCH (d:Decision)-[:GOVERNED_BY]->(p:Policy {policy_id: $policy_id})
        MATCH (d)-[:GRANTED]->(ex:Exception)
        OPTIONAL MATCH (ex)<-[:APPROVED]-(u:User)
        RETURN d.description AS description,
               d.decision_id AS decision_id,
               ex.exception_id AS exception_id,
               ex.reason AS reason,
               u.name AS approved_by,
               ex.approved_at AS approved_at
        ORDER BY ex.approved_at DESC
        LIMIT $limit
        """,
        {"policy_id": policy_id, "limit": limit},
    )


async def trace_causal_chain(
    client: GraphClient,
    outcome_id: str,
) -> list[GraphRecord]:
    """Walk backwards from an outcome to find the causal decision chain."""
    return await client.execute(
        """
        MATCH (o:Outcome {outcome_id: $outcome_id})
        MATCH (r:Run)-[:PRODUCED]->(o)
        MATCH (r)-[:CONTAINS]->(d:Decision)
        OPTIONAL MATCH (d)-[:CAUSED*0..5]->(caused:Decision)
        RETURN d.decision_id AS decision_id,
               d.description AS description,
               d.decision_type AS decision_type,
               d.timestamp AS timestamp,
               caused.decision_id AS caused_decision_id,
               caused.description AS caused_description
        ORDER BY d.timestamp
        """,
        {"outcome_id": outcome_id},
    )


async def detect_policy_gaps(
    client: GraphClient,
    *,
    limit: int = 20,
) -> list[GraphRecord]:
    """Find decisions that should have policy governance but don't.

    Targets decision types that typically require policy: policy_eval,
    exception, approval.
    """
    return await client.execute(
        """
        MATCH (d:Decision)
        WHERE NOT (d)-[:GOVERNED_BY]->(:Policy)
          AND d.decision_type IN ['policy_eval', 'exception', 'approval']
        RETURN d.decision_id AS decision_id,
               d.description AS description,
               d.decision_type AS decision_type,
               d.timestamp AS timestamp,
               d.reasoning AS reasoning
        ORDER BY d.timestamp DESC
        LIMIT $limit
        """,
        {"limit": limit},
    )


async def find_cross_entity_decisions(
    client: GraphClient,
    *,
    entity_a: str,
    entity_b: str,
) -> list[GraphRecord]:
    """Find decisions that reference both entity A and entity B."""
    return await client.execute(
        """
        MATCH (d:Decision)-[:REFERENCED]->(e1:Entity {entity_id: $entity_a})
        MATCH (d)-[:REFERENCED]->(e2:Entity {entity_id: $entity_b})
        RETURN d.decision_id AS decision_id,
               d.description AS description,
               d.decision_type AS decision_type,
               d.timestamp AS timestamp
        ORDER BY d.timestamp DESC
        """,
        {"entity_a": entity_a, "entity_b": entity_b},
    )
