use agileplus_domain::intent_graph::*;
use agileplus_trace_validator::validate_intent_graph;
use anyhow::Result;
use serde::{Deserialize, Serialize};

/// Report returned by graph validation.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct ValidationReport {
    pub valid: bool,
    pub errors: Vec<String>,
}

/// Validate a graph payload and return a structured report.
pub fn validate_graph_payload(graph: &IntentGraph) -> Result<ValidationReport> {
    match validate_intent_graph(graph) {
        Ok(()) => Ok(ValidationReport {
            valid: true,
            errors: Vec::new(),
        }),
        Err(errs) => Ok(ValidationReport {
            valid: false,
            errors: errs.iter().map(|e| e.to_string()).collect(),
        }),
    }
}

/// Query nodes in a graph, optionally filtering by type and status.
pub fn query_nodes(
    graph: &IntentGraph,
    node_type: Option<NodeType>,
    status: Option<Status>,
) -> Vec<&Node> {
    graph
        .nodes
        .iter()
        .filter(|n| {
            if let Some(nt) = node_type {
                n.node_type == nt
            } else {
                true
            }
        })
        .filter(|n| {
            if let Some(s) = status {
                n.status == s
            } else {
                true
            }
        })
        .collect()
}

/// Export a graph to Graphviz DOT format.
pub fn export_to_dot(graph: &IntentGraph) -> String {
    let mut lines = Vec::new();
    lines.push("digraph IntentGraph {".to_string());
    lines.push("    rankdir=TB;".to_string());
    lines.push(format!(
        "    label=\"{} (v{})\";",
        dot_escape(&graph.metadata.schema_uri),
        dot_escape(&graph.metadata.version)
    ));
    lines.push("    labelloc=t;".to_string());
    lines.push("    fontsize=12;".to_string());

    for node in &graph.nodes {
        let label = dot_escape(&node.title);
        let node_type = node.node_type.to_string();
        let status = node.status.to_string();
        lines.push(format!(
            "    \"{}\" [label=\"{}\" type=\"{}\" status=\"{}\"];",
            dot_escape(&node.id.0),
            label,
            node_type,
            status
        ));
    }

    for edge in &graph.edges {
        let rel = edge.relationship_type.to_string();
        lines.push(format!(
            "    \"{}\" -> \"{}\" [label=\"{}\"];",
            dot_escape(&edge.source.0),
            dot_escape(&edge.target.0),
            rel
        ));
    }

    lines.push("}".to_string());
    lines.join("\n")
}

/// Export a graph to JSON.
pub fn export_to_json(graph: &IntentGraph, pretty: bool) -> Result<String> {
    if pretty {
        Ok(serde_json::to_string_pretty(graph)?)
    } else {
        Ok(serde_json::to_string(graph)?)
    }
}

fn dot_escape(s: &str) -> String {
    s.replace('\\', "\\\\")
        .replace('\"', "\\\"")
        .replace('\n', "\\n")
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

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

    fn sample_graph() -> IntentGraph {
        let mut builder = IntentGraphBuilder::new().with_default_metadata();
        let intent = NodeBuilder::new()
            .id("Intent#test")
            .node_type(NodeType::Intent)
            .dag_stage(DagStage::Intent)
            .title("Test Intent")
            .status(Status::Active)
            .with_meta("test")
            .build()
            .unwrap();
        let feature = NodeBuilder::new()
            .id("Feature#feat")
            .node_type(NodeType::Feature)
            .dag_stage(DagStage::Feature)
            .title("Test Feature")
            .status(Status::Draft)
            .with_meta("test")
            .build()
            .unwrap();
        let test = NodeBuilder::new()
            .id("Test#feat")
            .node_type(NodeType::Test)
            .dag_stage(DagStage::Test)
            .title("Test Case")
            .status(Status::Draft)
            .with_meta("test")
            .build()
            .unwrap();
        builder = builder
            .node(intent.clone())
            .node(feature.clone())
            .node(test.clone());
        builder = builder.edge(
            EdgeBuilder::new()
                .auto_id()
                .source("Intent#test")
                .target("Feature#feat")
                .relationship_type(RelationshipType::Implements)
                .with_meta("test")
                .build()
                .unwrap(),
        );
        builder = builder.edge(
            EdgeBuilder::new()
                .auto_id()
                .source("Feature#feat")
                .target("Test#feat")
                .relationship_type(RelationshipType::Tests)
                .with_meta("test")
                .build()
                .unwrap(),
        );
        builder.build().unwrap()
    }

    #[test]
    fn test_validate_graph_payload() {
        let graph = sample_graph();
        let report = validate_graph_payload(&graph).unwrap();
        assert!(report.valid);
        assert!(report.errors.is_empty());
    }

    #[test]
    fn test_query_nodes_by_type() {
        let graph = sample_graph();
        let intents = query_nodes(&graph, Some(NodeType::Intent), None);
        assert_eq!(intents.len(), 1);
        assert_eq!(intents[0].id.0, "Intent#test");
    }

    #[test]
    fn test_query_nodes_by_status() {
        let graph = sample_graph();
        let drafts = query_nodes(&graph, None, Some(Status::Draft));
        assert_eq!(drafts.len(), 2);
    }

    #[test]
    fn test_export_to_dot() {
        let graph = sample_graph();
        let dot = export_to_dot(&graph);
        assert!(dot.contains("digraph IntentGraph"));
        assert!(dot.contains("\"Intent#test\""));
        assert!(dot.contains("\"Feature#feat\""));
        assert!(dot.contains("->"));
    }

    #[test]
    fn test_export_to_json() {
        let graph = sample_graph();
        let json = export_to_json(&graph, true).unwrap();
        assert!(json.contains("Intent#test"));
        assert!(json.contains("nodes"));
        assert!(json.contains("edges"));
    }
}
