//! Built-in Tool: db_graph_query — Phase-83 T83.3
//!
//! Performs graph traversal and relationship queries on SocialGraphV2.
//! Supports: get entity, list relations, get neighbors by relation type.

use std::sync::Arc;

use async_trait::async_trait;
use serde::Deserialize;

use crate::social::SocialStoreV2;
use crate::llm::error::{LlmError, LlmResult};
use crate::llm::tool_executor::{ToolExecutor, ToolMetadata, ToolResult};

/// Tool for graph traversal and relationship queries.
pub struct GraphQueryTool {
    graph: Arc<SocialStoreV2>,
}

impl GraphQueryTool {
    /// Create a new graph query tool.
    pub fn new(graph: Arc<SocialStoreV2>) -> Self {
        Self { graph }
    }
}

/// Arguments for the graph query tool.
#[derive(Debug, Deserialize)]
struct GraphQueryArgs {
    /// The operation to perform.
    operation: GraphOperation,

    /// Entity ID for the query (required for most operations).
    #[serde(default)]
    entity_id: Option<String>,

    /// Entity type filter (e.g., "person", "company", "tag").
    #[serde(default)]
    entity_type: Option<String>,

    /// Relation type filter (e.g., "friend", "colleague", "has_tag").
    #[serde(default)]
    relation_type: Option<String>,

    /// Direction of relations to query.
    #[serde(default = "default_direction")]
    direction: RelationDirection,

    /// Maximum number of results (default: 20).
    #[serde(default = "default_limit")]
    limit: usize,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
enum GraphOperation {
    /// Get a single entity by ID.
    GetEntity,
    /// Get relations of an entity.
    GetRelations,
    /// Get neighbor entities connected by relations.
    GetNeighbors,
    /// List entities by type.
    ListEntities,
}

#[derive(Debug, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
enum RelationDirection {
    /// Outgoing relations (entity → target).
    Outgoing,
    /// Incoming relations (source → entity).
    Incoming,
    /// Both directions.
    #[default]
    Both,
}

fn default_direction() -> RelationDirection {
    RelationDirection::Both
}

fn default_limit() -> usize {
    20
}

#[async_trait]
impl ToolExecutor for GraphQueryTool {
    fn metadata(&self) -> ToolMetadata {
        ToolMetadata::new(
            "db_graph_query",
            "Query the knowledge graph. Supports operations: \
             get_entity (get entity by ID), \
             get_relations (get relations of an entity), \
             get_neighbors (get neighbor entities connected via relations), \
             list_entities (list entities of a given type). \
             Supports filtering by relation_type and direction (outgoing/incoming/both).",
            serde_json::json!({
                "type": "object",
                "properties": {
                    "operation": {
                        "type": "string",
                        "enum": ["get_entity", "get_relations", "get_neighbors", "list_entities"],
                        "description": "The graph operation to perform"
                    },
                    "entity_id": {
                        "type": "string",
                        "description": "Entity ID (required for get_entity, get_relations, get_neighbors)"
                    },
                    "entity_type": {
                        "type": "string",
                        "description": "Entity type filter (e.g., 'person', 'company', 'tag'). Required for list_entities."
                    },
                    "relation_type": {
                        "type": "string",
                        "description": "Filter relations by type (e.g., 'friend', 'has_tag')"
                    },
                    "direction": {
                        "type": "string",
                        "enum": ["outgoing", "incoming", "both"],
                        "description": "Direction of relations to query (default: both)",
                        "default": "both"
                    },
                    "limit": {
                        "type": "integer",
                        "description": "Maximum number of results (default: 20)",
                        "default": 20,
                        "minimum": 1,
                        "maximum": 200
                    }
                },
                "required": ["operation"]
            }),
        )
        .with_category("graph")
    }

    async fn execute(&self, arguments: &str) -> LlmResult<ToolResult> {
        let args: GraphQueryArgs = serde_json::from_str(arguments)
            .map_err(|e| LlmError::ResponseParseError(format!("Invalid arguments: {}", e)))?;

        let limit = args.limit.min(200).max(1);

        match args.operation {
            GraphOperation::GetEntity => {
                let id = args.entity_id.as_deref().ok_or_else(|| {
                    LlmError::ResponseParseError("entity_id is required for get_entity".into())
                })?;

                match self.graph.get_entity_by_id(id) {
                    Some(entity) => Ok(ToolResult::ok_json(&serde_json::json!({
                        "found": true,
                        "entity": {
                            "id": entity.id,
                            "entity_type": entity.entity_type,
                            "name": entity.name,
                            "properties": entity.properties,
                        }
                    }))),
                    None => Ok(ToolResult::ok_json(&serde_json::json!({
                        "found": false,
                        "message": format!("Entity '{}' not found", id)
                    }))),
                }
            }

            GraphOperation::GetRelations => {
                let id = args.entity_id.as_deref().ok_or_else(|| {
                    LlmError::ResponseParseError("entity_id is required for get_relations".into())
                })?;

                let mut relations = Vec::new();

                // Outgoing
                if matches!(args.direction, RelationDirection::Outgoing | RelationDirection::Both) {
                    let outgoing = self.graph.outgoing_from(id);
                    for r in outgoing {
                        if let Some(ref rt) = args.relation_type {
                            if r.relation_type != *rt {
                                continue;
                            }
                        }
                        relations.push(serde_json::json!({
                            "direction": "outgoing",
                            "source": r.source,
                            "target": r.target,
                            "relation_type": r.relation_type,
                            "weight": r.weight,
                            "metadata": r.metadata,
                        }));
                    }
                }

                // Incoming
                if matches!(args.direction, RelationDirection::Incoming | RelationDirection::Both) {
                    let incoming = self.graph.incoming_to(id);
                    for r in incoming {
                        if let Some(ref rt) = args.relation_type {
                            if r.relation_type != *rt {
                                continue;
                            }
                        }
                        relations.push(serde_json::json!({
                            "direction": "incoming",
                            "source": r.source,
                            "target": r.target,
                            "relation_type": r.relation_type,
                            "weight": r.weight,
                            "metadata": r.metadata,
                        }));
                    }
                }

                relations.truncate(limit);

                Ok(ToolResult::ok_json(&serde_json::json!({
                    "entity_id": id,
                    "relation_count": relations.len(),
                    "relations": relations,
                })))
            }

            GraphOperation::GetNeighbors => {
                let id = args.entity_id.as_deref().ok_or_else(|| {
                    LlmError::ResponseParseError("entity_id is required for get_neighbors".into())
                })?;

                let mut neighbor_ids = Vec::new();

                // Outgoing neighbors
                if matches!(args.direction, RelationDirection::Outgoing | RelationDirection::Both) {
                    for r in self.graph.outgoing_from(id) {
                        if let Some(ref rt) = args.relation_type {
                            if r.relation_type != *rt {
                                continue;
                            }
                        }
                        neighbor_ids.push(r.target);
                    }
                }

                // Incoming neighbors
                if matches!(args.direction, RelationDirection::Incoming | RelationDirection::Both) {
                    for r in self.graph.incoming_to(id) {
                        if let Some(ref rt) = args.relation_type {
                            if r.relation_type != *rt {
                                continue;
                            }
                        }
                        neighbor_ids.push(r.source);
                    }
                }

                // Deduplicate
                neighbor_ids.sort();
                neighbor_ids.dedup();
                neighbor_ids.truncate(limit);

                // Resolve entities
                let neighbors: Vec<serde_json::Value> = neighbor_ids
                    .iter()
                    .filter_map(|nid| {
                        self.graph.get_entity_by_id(nid).map(|e| {
                            serde_json::json!({
                                "id": e.id,
                                "entity_type": e.entity_type,
                                "name": e.name,
                                "properties": e.properties,
                            })
                        })
                    })
                    .collect();

                Ok(ToolResult::ok_json(&serde_json::json!({
                    "entity_id": id,
                    "neighbor_count": neighbors.len(),
                    "neighbors": neighbors,
                })))
            }

            GraphOperation::ListEntities => {
                let entity_type = args.entity_type.as_deref().ok_or_else(|| {
                    LlmError::ResponseParseError(
                        "entity_type is required for list_entities".into(),
                    )
                })?;

                let entities = self.graph.list_entities_by_type(entity_type);
                let total = entities.len();
                let result: Vec<serde_json::Value> = entities
                    .into_iter()
                    .take(limit)
                    .map(|e| {
                        serde_json::json!({
                            "id": e.id,
                            "entity_type": e.entity_type,
                            "name": e.name,
                        })
                    })
                    .collect();

                Ok(ToolResult::ok_json(&serde_json::json!({
                    "entity_type": entity_type,
                    "total": total,
                    "result_count": result.len(),
                    "entities": result,
                })))
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_graph_query_metadata() {
        use crate::social::SocialStoreV2Config;
        let config = SocialStoreV2Config::new(std::env::temp_dir().join("test_gq_meta"));
        let graph = Arc::new(SocialStoreV2::new(config));
        let tool = GraphQueryTool::new(graph);
        let meta = tool.metadata();
        assert_eq!(meta.name, "db_graph_query");
        assert_eq!(meta.category, "graph");
        assert!(meta.is_read_only);
    }
}
