//! Integration tests for MCP storage-backed tools.
//!
//! Each test opens a temporary on-disk SQLite database and calls the
//! sync tool function directly — no HTTP server or FastMCP client needed.

use agileplus_domain::builder::{
    EdgeBuilder, IntentGraphBuilder, NodeBuilder,
};
use agileplus_domain::intent_graph::{DagStage, NodeType, RelationshipType, Status};
use agileplus_mcp_intent::api::McpToolResponse;
use agileplus_sqlite::Storage;
use serde_json::{json, Value};
use std::path::PathBuf;

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

fn tmp_db(name: &str) -> PathBuf {
    let mut p = std::env::temp_dir();
    p.push(format!("agileplus_mcp_test_{}", name));
    let _ = std::fs::remove_file(&p);
    p
}

fn sample_graph() -> agileplus_domain::IntentGraph {
    IntentGraphBuilder::default()
        .with_default_metadata()
        .node(
            NodeBuilder::new()
                .id("Plan#implement-mcp")
                .node_type(NodeType::Task)
                .dag_stage(DagStage::Task)
                .title("Implement MCP adapter")
                .status(Status::Active)
                .with_meta("test")
                .build()
                .unwrap(),
        )
        .node(
            NodeBuilder::new()
                .id("Code#write-tools")
                .node_type(NodeType::Task)
                .dag_stage(DagStage::Spec)
                .title("Write MCP tool definitions")
                .status(Status::Active)
                .with_meta("test")
                .build()
                .unwrap(),
        )
        .node(
            NodeBuilder::new()
                .id("Test#mcp-tests")
                .node_type(NodeType::Task)
                .dag_stage(DagStage::Test)
                .title("Add integration tests")
                .status(Status::Active)
                .with_meta("test")
                .build()
                .unwrap(),
        )
        .edge(
            EdgeBuilder::new()
                .auto_id()
                .source("Plan#implement-mcp")
                .target("Code#write-tools")
                .relationship_type(RelationshipType::DependsOn)
                .with_meta("test")
                .build()
                .unwrap(),
        )
        .edge(
            EdgeBuilder::new()
                .auto_id()
                .source("Code#write-tools")
                .target("Test#mcp-tests")
                .relationship_type(RelationshipType::DependsOn)
                .with_meta("test")
                .build()
                .unwrap(),
        )
        .build()
        .expect("sample graph should build")
}

fn open_storage(db: &std::path::Path) -> Storage {
    Storage::open(db.to_str().unwrap()).expect("open storage")
}

/// Get the "id" field from a non-error McpToolResponse JSON text.
fn first_id(resp: &McpToolResponse) -> String {
    assert!(!resp.is_error, "response is error: {:?}", resp);
    let val: Value =
        serde_json::from_str(&resp.content[0].text).expect("response is valid JSON");
    val["id"].as_str().unwrap_or("").to_string()
}

fn count_ids(resp: &McpToolResponse) -> usize {
    assert!(!resp.is_error);
    let val: Value =
        serde_json::from_str(&resp.content[0].text).expect("response is valid JSON");
    val["ids"].as_array().map(|a| a.len()).unwrap_or(0)
}

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

#[test]
fn test_tool_create_and_get_graph() {
    let db = tmp_db("save_get");
    let store = open_storage(&db);
    let graph = sample_graph();
    let graph_json = serde_json::to_value(&graph).unwrap();

    // Create
    let resp = agileplus_mcp_intent::tools::create_graph(
        &store,
        json!({"id": "test-1", "graph": graph_json}),
    );
    assert!(!resp.is_error, "create should succeed: {:?}", resp);
    let id = first_id(&resp);
    assert!(!id.is_empty(), "should return id");

    // Get
    let resp = agileplus_mcp_intent::tools::get_graph(&store, json!({"id": id}));
    assert!(!resp.is_error, "get should succeed: {:?}", resp);
    let body: Value = serde_json::from_str(&resp.content[0].text).unwrap();
    assert!(body["nodes"].is_array(), "graph should have nodes");
}

#[test]
fn test_tool_list_graphs() {
    let db = tmp_db("list");
    let store = open_storage(&db);
    let graph = sample_graph();

    let resp = agileplus_mcp_intent::tools::create_graph(
        &store,
        json!({"id": "list-1", "graph": graph}),
    );
    assert!(!resp.is_error);

    let resp = agileplus_mcp_intent::tools::list_graphs(&store, json!({}));
    assert!(!resp.is_error);
    assert_eq!(count_ids(&resp), 1, "should list 1 graph");
}

#[test]
fn test_tool_delete_graph() {
    let db = tmp_db("delete");
    let store = open_storage(&db);
    let graph = sample_graph();

    let resp = agileplus_mcp_intent::tools::create_graph(
        &store,
        json!({"id": "del-1", "graph": graph}),
    );
    assert!(!resp.is_error);
    let id = first_id(&resp);

    let resp = agileplus_mcp_intent::tools::delete_graph(&store, json!({"id": id}));
    assert!(!resp.is_error, "delete should succeed: {:?}", resp);

    let resp = agileplus_mcp_intent::tools::list_graphs(&store, json!({}));
    assert!(!resp.is_error);
    assert_eq!(count_ids(&resp), 0, "no graphs after delete");
}

#[test]
fn test_tool_get_missing_graph() {
    let db = tmp_db("missing");
    let store = open_storage(&db);

    let resp = agileplus_mcp_intent::tools::get_graph(&store, json!({"id": "no-such"}));
    assert!(resp.is_error, "should error on missing graph");
}

#[test]
fn test_tool_delete_missing_graph() {
    let db = tmp_db("del_missing");
    let store = open_storage(&db);

    let resp =
        agileplus_mcp_intent::tools::delete_graph(&store, json!({"id": "no-such"}));
    assert!(!resp.is_error, "delete of missing graph is idempotent");
    let content: serde_json::Value =
        serde_json::from_str(&resp.content[0].text).expect("valid json content");
    assert_eq!(content["deleted"], false);
}

// ---------------------------------------------------------------------------
// Tags
// ---------------------------------------------------------------------------

#[test]
fn test_tool_tag_lifecycle() {
    let db = tmp_db("tags");
    let store = open_storage(&db);
    let graph = sample_graph();

    let id = first_id(&agileplus_mcp_intent::tools::create_graph(
        &store,
        json!({"id": "tag-test", "graph": graph}),
    ));

    // add
    let resp = agileplus_mcp_intent::tools::add_tag(
        &store,
        json!({"graph_id": id, "tag": "important"}),
    );
    assert!(!resp.is_error, "add_tag: {:?}", resp);

    // add another
    let resp = agileplus_mcp_intent::tools::add_tag(
        &store,
        json!({"graph_id": id, "tag": "urgent"}),
    );
    assert!(!resp.is_error);

    // list
    let resp = agileplus_mcp_intent::tools::list_tags(&store, json!({"graph_id": id}));
    assert!(!resp.is_error);
    let body: Value = serde_json::from_str(&resp.content[0].text).unwrap();
    let tags = body["tags"].as_array().unwrap();
    assert_eq!(tags.len(), 2, "should have 2 tags");

    // remove
    let resp = agileplus_mcp_intent::tools::remove_tag(
        &store,
        json!({"graph_id": id, "tag": "important"}),
    );
    assert!(!resp.is_error);

    let resp = agileplus_mcp_intent::tools::list_tags(&store, json!({"graph_id": id}));
    assert!(!resp.is_error);
    let body: Value = serde_json::from_str(&resp.content[0].text).unwrap();
    let tags = body["tags"].as_array().unwrap();
    assert_eq!(tags.len(), 1, "only urgent remains");
}

#[test]
fn test_tool_add_duplicate_tag() {
    let db = tmp_db("dup_tag");
    let store = open_storage(&db);
    let graph = sample_graph();

    let id = first_id(&agileplus_mcp_intent::tools::create_graph(
        &store,
        json!({"id": "dup", "graph": graph}),
    ));

    agileplus_mcp_intent::tools::add_tag(&store, json!({"graph_id": id, "tag": "dup-tag"}));
    let resp = agileplus_mcp_intent::tools::add_tag(
        &store,
        json!({"graph_id": id, "tag": "dup-tag"}),
    );
    assert!(!resp.is_error, "duplicate add should be idempotent");
}

// ---------------------------------------------------------------------------
// Notes
// ---------------------------------------------------------------------------

#[test]
fn test_tool_note_lifecycle() {
    let db = tmp_db("notes");
    let store = open_storage(&db);
    let graph = sample_graph();

    let id = first_id(&agileplus_mcp_intent::tools::create_graph(
        &store,
        json!({"id": "note-test", "graph": graph}),
    ));

    // add two notes
    let resp = agileplus_mcp_intent::tools::add_note(
        &store,
        json!({"graph_id": id, "body": "First note body"}),
    );
    assert!(!resp.is_error, "add_note 1: {:?}", resp);
    let body: Value = serde_json::from_str(&resp.content[0].text).unwrap();
    let note_id = body["note_id"].as_i64().unwrap_or(0);

    let resp = agileplus_mcp_intent::tools::add_note(
        &store,
        json!({"graph_id": id, "body": "Second note body"}),
    );
    assert!(!resp.is_error, "add_note 2");

    // list
    let resp = agileplus_mcp_intent::tools::list_notes(&store, json!({"graph_id": id}));
    assert!(!resp.is_error);
    let body: Value = serde_json::from_str(&resp.content[0].text).unwrap();
    let notes = body["notes"].as_array().unwrap();
    assert_eq!(notes.len(), 2, "should have 2 notes");

    // delete
    let resp = agileplus_mcp_intent::tools::delete_note(
        &store,
        json!({"graph_id": id, "note_id": note_id}),
    );
    assert!(!resp.is_error, "delete_note: {:?}", resp);

    let resp = agileplus_mcp_intent::tools::list_notes(&store, json!({"graph_id": id}));
    assert!(!resp.is_error);
    let body: Value = serde_json::from_str(&resp.content[0].text).unwrap();
    let notes = body["notes"].as_array().unwrap();
    assert_eq!(notes.len(), 1, "only second note remains");
}

// ---------------------------------------------------------------------------
// validate_stored_graph
// ---------------------------------------------------------------------------

#[test]
fn test_tool_validate_stored_graph() {
    let db = tmp_db("validate");
    let store = open_storage(&db);
    let graph = sample_graph();

    let id = first_id(&agileplus_mcp_intent::tools::create_graph(
        &store,
        json!({"id": "val-1", "graph": graph}),
    ));

    let resp =
        agileplus_mcp_intent::tools::validate_stored_graph(&store, json!({"id": id}));
    assert!(!resp.is_error, "validate: {:?}", resp);
}

#[test]
fn test_tool_validate_missing_graph() {
    let db = tmp_db("validate_missing");
    let store = open_storage(&db);

    let resp = agileplus_mcp_intent::tools::validate_stored_graph(
        &store,
        json!({"id": "no-such"}),
    );
    assert!(resp.is_error, "should error on missing graph");
}

// ---------------------------------------------------------------------------
// Edge case: empty graph
// ---------------------------------------------------------------------------

#[test]
fn test_tool_save_empty_graph() {
    let db = tmp_db("empty");
    let store = open_storage(&db);

    let empty = IntentGraphBuilder::default()
        .with_default_metadata()
        .build()
        .expect("empty graph should build");

    let resp = agileplus_mcp_intent::tools::create_graph(
        &store,
        json!({"id": "empty", "graph": empty}),
    );
    assert!(!resp.is_error, "save empty graph: {:?}", resp);
    let id = first_id(&resp);
    assert!(!id.is_empty(), "should return an id");
}
