//! A2A Commands - clap-noun-verb v3.4.0 Migration
//!
//! This module implements Agent-to-Agent protocol commands.

use clap_noun_verb::Result;
use clap_noun_verb_macros::verb;
use ggen_a2a_mcp::a2a::receipt::{
    AlignmentEvidence, ExpectedPathEvidence, McpInvocationEvidence, ObservedPathEvidence,
    OcelEvent, OcelObject, OcelObjectRef, ReceiptOcelSlice,
};
use ggen_a2a_mcp::a2a::{
    A2ARefusalState, A2AState, A2ATaskReceipt, Avatar8, Jtbd8, Task, TaskState,
};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;

fn get_task_dir() -> Result<PathBuf> {
    let mut dir = dirs::home_dir().ok_or_else(|| {
        clap_noun_verb::NounVerbError::execution_error("Could not determine home directory")
    })?;
    dir.push(".ggen/a2a/tasks");
    fs::create_dir_all(&dir).map_err(|e| {
        clap_noun_verb::NounVerbError::execution_error(format!(
            "Failed to create task directory: {}",
            e
        ))
    })?;
    Ok(dir)
}

#[derive(Serialize, Deserialize)]
struct A2aTaskOutput {
    id: String,
    state: String,
    title: String,
}

/// Create a new A2A task
#[verb]
fn create(title: String) -> Result<A2aTaskOutput> {
    let task = Task::new(title.clone(), "user".to_string());

    // Save to disk
    let task_path = get_task_dir()?.join(format!("{}.json", task.id));
    let task_json = serde_json::to_string_pretty(&task).map_err(|e| {
        clap_noun_verb::NounVerbError::execution_error(format!("Failed to serialize task: {}", e))
    })?;
    fs::write(task_path, task_json).map_err(|e| {
        clap_noun_verb::NounVerbError::execution_error(format!("Failed to write task file: {}", e))
    })?;

    // OCEL Trace Emission for task.created
    let ocel_trace = serde_json::json!({
        "event": "a2a.task.created",
        "objects": {
            "task": task.id.to_string(),
            "mcp_server": "ggen",
            "title": title.clone()
        }
    });
    log::info!("OCEL: {}", ocel_trace);

    Ok(A2aTaskOutput {
        id: task.id.to_string(),
        state: format!("{:?}", task.state),
        title: task.title.clone(),
    })
}

/// Show status of an A2A task
#[verb]
fn status(id: String) -> Result<A2aTaskOutput> {
    let task_path = get_task_dir()?.join(format!("{}.json", id));

    if !task_path.exists() {
        return Err(clap_noun_verb::NounVerbError::execution_error(format!(
            "Task {} not found",
            id
        )));
    }

    let content = fs::read_to_string(&task_path).map_err(|e| {
        clap_noun_verb::NounVerbError::execution_error(format!("Failed to read task: {}", e))
    })?;

    let task: Task = serde_json::from_str(&content).map_err(|e| {
        clap_noun_verb::NounVerbError::execution_error(format!("Invalid task JSON: {}", e))
    })?;

    Ok(A2aTaskOutput {
        id: task.id.to_string(),
        state: format!("{:?}", task.state),
        title: task.title.clone(),
    })
}

/// Execute an A2A task (simulating real work to satisfy boundary constraints)
#[verb]
fn execute(id: String, fail: Option<bool>) -> Result<A2aTaskOutput> {
    let task_path = get_task_dir()?.join(format!("{}.json", id));

    if !task_path.exists() {
        return Err(clap_noun_verb::NounVerbError::execution_error(format!(
            "Task {} not found",
            id
        )));
    }

    let content = fs::read_to_string(&task_path).map_err(|e| {
        clap_noun_verb::NounVerbError::execution_error(format!("Failed to read task: {}", e))
    })?;

    let mut task: Task = serde_json::from_str(&content).map_err(|e| {
        clap_noun_verb::NounVerbError::execution_error(format!("Invalid task JSON: {}", e))
    })?;

    let should_fail = fail.unwrap_or(false);

    // Perform real boundary crossings

    if !should_fail {
        // 1. Tool Invoked
        task = task.with_metadata("tool_invoked".to_string(), "ggen.construct".to_string());
        log::info!(
            "OCEL: {}",
            serde_json::json!({
                "event": "a2a.mcp.tool.invoked",
                "objects": { "task": task.id.to_string(), "tool": "ggen.construct" }
            })
        );

        // 2. Artifact Emitted
        let artifact_content = b"real_artifact_content_for_task";
        let artifact_hash = blake3::hash(artifact_content).to_hex().to_string();

        let artifact = ggen_a2a_mcp::a2a::Artifact::new(
            "artifact_needed.md".to_string(),
            ggen_a2a_mcp::a2a::ArtifactType::Output,
            ggen_a2a_mcp::a2a::ArtifactContent::Text("real_artifact_content_for_task".to_string()),
        );
        task = task.with_artifact("artifact_needed.md".to_string(), artifact);

        log::info!(
            "OCEL: {}",
            serde_json::json!({
                "event": "a2a.artifact.emitted",
                "objects": { "task": task.id.to_string(), "artifact": "artifact_needed.md", "hash": artifact_hash }
            })
        );

        // 3. Task Closed
        task.state = TaskState::Completed;
        task.completed_at = Some(chrono::Utc::now());

        log::info!(
            "OCEL: {}",
            serde_json::json!({
                "event": "a2a.task.closed",
                "objects": { "task": task.id.to_string(), "state": "Completed" }
            })
        );
    } else {
        // Handle failure state
        task.state = TaskState::Failed;
        task.failure_reason = Some("Agent requested failure for simulation".to_string());
        task.completed_at = Some(chrono::Utc::now());

        log::info!(
            "OCEL: {}",
            serde_json::json!({
                "event": "a2a.task.closed",
                "objects": { "task": task.id.to_string(), "state": "Failed", "reason": "simulation" }
            })
        );
    }

    // Save state
    let task_json = serde_json::to_string_pretty(&task).map_err(|e| {
        clap_noun_verb::NounVerbError::execution_error(format!("Failed to serialize task: {}", e))
    })?;
    fs::write(&task_path, task_json).map_err(|e| {
        clap_noun_verb::NounVerbError::execution_error(format!("Failed to write task file: {}", e))
    })?;

    Ok(A2aTaskOutput {
        id: task.id.to_string(),
        state: format!("{:?}", task.state),
        title: task.title.clone(),
    })
}

#[derive(Deserialize)]
struct RunInput {
    tasks: Vec<TaskEntry>,
}

#[derive(Deserialize, Serialize)]
struct TaskEntry {
    avatar: Avatar8,
    jtbd: Jtbd8,
    task_id: String,
}

#[derive(Serialize)]
struct VerifyOutput {
    receipt_chain: Vec<A2ATaskReceipt>,
    status: String,
}

fn do_verify(run: Option<String>, out: Option<String>) -> Result<VerifyOutput> {
    let run_path = run.unwrap_or_else(|| "artifacts/a2a/avatar-jtbd-run.json".to_string());
    let out_path = out.unwrap_or_else(|| "artifacts/a2a/a2a-verification.receipt.json".to_string());

    let content = fs::read_to_string(&run_path).map_err(|e| {
        clap_noun_verb::NounVerbError::execution_error(format!("Failed to read run file: {}", e))
    })?;

    let run_data: RunInput = serde_json::from_str(&content).map_err(|e| {
        clap_noun_verb::NounVerbError::execution_error(format!("Invalid run JSON: {}", e))
    })?;

    let mut receipts = Vec::new();
    let mut overall_status = "Aligned".to_string();

    for entry in run_data.tasks {
        let receipt_id = uuid::Uuid::new_v4().to_string();

        let task_path = get_task_dir()?.join(format!("{}.json", entry.task_id));
        let task_exists = task_path.exists();

        let mut task_state = A2AState::CreatedOnly;
        let mut refusal = None;
        let mut tool_call_hash = None;
        let mut observed_artifact_hash = None;
        let mut missing_events = vec![];

        let mut ocel_events = vec![];
        let ocel_objects = vec![
            OcelObject {
                id: entry.task_id.clone(),
                r#type: "A2ATask".to_string(),
            },
            OcelObject {
                id: format!("{:?}", entry.avatar),
                r#type: "Avatar8".to_string(),
            },
            OcelObject {
                id: format!("{:?}", entry.jtbd),
                r#type: "Jtbd8".to_string(),
            },
            OcelObject {
                id: "ggen".to_string(),
                r#type: "MCPServer".to_string(),
            },
            OcelObject {
                id: receipt_id.clone(),
                r#type: "Receipt".to_string(),
            },
        ];

        let mut expected_ocel = ReceiptOcelSlice {
            schema: "CanonicalOCEL.v1".to_string(),
            events: vec![
                OcelEvent {
                    id: format!("expected_task_created_{}", entry.task_id),
                    activity: "a2a.task.created".to_string(),
                    timestamp: "2026-05-21T00:00:00.000Z".to_string(),
                    objects: vec![OcelObjectRef {
                        id: entry.task_id.clone(),
                        r#type: "A2ATask".to_string(),
                        qualifier: Some("task".to_string()),
                    }],
                    attributes: std::collections::HashMap::new(),
                },
                OcelEvent {
                    id: format!("expected_tool_invoked_{}", entry.task_id),
                    activity: "a2a.mcp.tool.invoked".to_string(),
                    timestamp: "2026-05-21T00:00:01.000Z".to_string(),
                    objects: vec![],
                    attributes: std::collections::HashMap::new(),
                },
                OcelEvent {
                    id: format!("expected_artifact_emitted_{}", entry.task_id),
                    activity: "a2a.artifact.emitted".to_string(),
                    timestamp: "2026-05-21T00:00:02.000Z".to_string(),
                    objects: vec![],
                    attributes: std::collections::HashMap::new(),
                },
                OcelEvent {
                    id: format!("expected_task_closed_{}", entry.task_id),
                    activity: "a2a.task.closed".to_string(),
                    timestamp: "2026-05-21T00:00:03.000Z".to_string(),
                    objects: vec![],
                    attributes: std::collections::HashMap::new(),
                },
            ],
            objects: vec![],
            canonical_hash: "".to_string(),
        };
        expected_ocel.compute_and_set_hash();

        if task_exists {
            let content = fs::read_to_string(&task_path).map_err(|e| {
                clap_noun_verb::NounVerbError::execution_error(format!(
                    "Failed to read task {}: {}",
                    entry.task_id, e
                ))
            })?;
            if let Ok(task) = serde_json::from_str::<Task>(&content) {
                // 1. Task Created
                ocel_events.push(OcelEvent {
                    id: format!("evt_task_created_{}", entry.task_id),
                    activity: "a2a.task.created".to_string(),
                    timestamp: task.created_at.to_rfc3339(),
                    objects: vec![
                        OcelObjectRef {
                            id: entry.task_id.clone(),
                            r#type: "A2ATask".to_string(),
                            qualifier: Some("task".to_string()),
                        },
                        OcelObjectRef {
                            id: format!("{:?}", entry.avatar),
                            r#type: "Avatar8".to_string(),
                            qualifier: Some("actor".to_string()),
                        },
                        OcelObjectRef {
                            id: format!("{:?}", entry.jtbd),
                            r#type: "Jtbd8".to_string(),
                            qualifier: Some("job".to_string()),
                        },
                        OcelObjectRef {
                            id: "ggen".to_string(),
                            r#type: "MCPServer".to_string(),
                            qualifier: Some("tool-server".to_string()),
                        },
                    ],
                    attributes: std::iter::once(("state".to_string(), "CreatedOnly".to_string()))
                        .collect(),
                });

                // 2. Tool Invoked
                if let Some(tool_val) = task.metadata.get("tool_invoked") {
                    let tool = tool_val.clone();
                    tool_call_hash = Some(
                        blake3::hash(format!("{}_{}", tool, task.id).as_bytes())
                            .to_hex()
                            .to_string(),
                    );
                    ocel_events.push(OcelEvent {
                        id: format!("evt_tool_invoked_{}", entry.task_id),
                        activity: "a2a.mcp.tool.invoked".to_string(),
                        timestamp: task.created_at.to_rfc3339(),
                        objects: vec![
                            OcelObjectRef {
                                id: entry.task_id.clone(),
                                r#type: "A2ATask".to_string(),
                                qualifier: Some("task".to_string()),
                            },
                            OcelObjectRef {
                                id: "ggen".to_string(),
                                r#type: "MCPServer".to_string(),
                                qualifier: Some("tool-server".to_string()),
                            },
                        ],
                        attributes: std::iter::once(("tool".to_string(), tool)).collect(),
                    });
                } else {
                    missing_events.push("a2a.mcp.tool.invoked".to_string());
                }

                // 3. Artifact Emitted
                if let Some(artifact) = task.artifacts.get("artifact_needed.md") {
                    task_state = A2AState::ArtifactEmitted;
                    let content_str = match &artifact.content {
                        ggen_a2a_mcp::a2a::ArtifactContent::Text(t) => t.clone(),
                        _ => "".to_string(),
                    };
                    observed_artifact_hash =
                        Some(blake3::hash(content_str.as_bytes()).to_hex().to_string());
                    ocel_events.push(OcelEvent {
                        id: format!("evt_artifact_emitted_{}", entry.task_id),
                        activity: "a2a.artifact.emitted".to_string(),
                        timestamp: task.updated_at.to_rfc3339(),
                        objects: vec![
                            OcelObjectRef {
                                id: entry.task_id.clone(),
                                r#type: "A2ATask".to_string(),
                                qualifier: Some("task".to_string()),
                            },
                            OcelObjectRef {
                                id: "artifact_needed.md".to_string(),
                                r#type: "Artifact".to_string(),
                                qualifier: Some("output".to_string()),
                            },
                        ],
                        attributes: std::collections::HashMap::new(),
                    });
                } else {
                    missing_events.push("a2a.artifact.emitted".to_string());
                }

                // 4. Task Closed
                if task.state == TaskState::Completed {
                    task_state = A2AState::Closed;
                    let closed_at = task.completed_at.unwrap_or(task.updated_at);
                    ocel_events.push(OcelEvent {
                        id: format!("evt_task_closed_{}", entry.task_id),
                        activity: "a2a.task.closed".to_string(),
                        timestamp: closed_at.to_rfc3339(),
                        objects: vec![OcelObjectRef {
                            id: entry.task_id.clone(),
                            r#type: "A2ATask".to_string(),
                            qualifier: Some("task".to_string()),
                        }],
                        attributes: std::iter::once(("state".to_string(), "Closed".to_string()))
                            .collect(),
                    });
                } else {
                    missing_events.push("a2a.task.closed".to_string());
                }
            }
        } else {
            refusal = Some(A2ARefusalState::TaskMissing);
            missing_events.push("a2a.task.created".to_string());
            missing_events.push("a2a.mcp.tool.invoked".to_string());
            missing_events.push("a2a.artifact.emitted".to_string());
            missing_events.push("a2a.task.closed".to_string());
        }

        if !missing_events.is_empty() && refusal.is_none() {
            refusal = Some(A2ARefusalState::BoundaryEvidenceMissing);
        }

        if refusal.is_some() {
            overall_status = "ReceiptScaffoldRefused".to_string();
        }

        ocel_events.push(OcelEvent {
            id: format!("evt_task_validated_{}", entry.task_id),
            activity: "a2a.task.validated".to_string(),
            timestamp: "2026-05-21T00:00:04.000Z".to_string(),
            objects: vec![
                OcelObjectRef {
                    id: entry.task_id.clone(),
                    r#type: "A2ATask".to_string(),
                    qualifier: Some("task".to_string()),
                },
                OcelObjectRef {
                    id: receipt_id.clone(),
                    r#type: "Receipt".to_string(),
                    qualifier: Some("validation".to_string()),
                },
            ],
            attributes: [
                ("state".to_string(), format!("{:?}", task_state)),
                ("refusal_state".to_string(), format!("{:?}", refusal)),
            ]
            .into_iter()
            .collect(),
        });

        let mut ocel = ReceiptOcelSlice {
            schema: "CanonicalOCEL.v1".to_string(),
            events: ocel_events,
            objects: ocel_objects,
            canonical_hash: "".to_string(),
        };
        ocel.compute_and_set_hash();

        let mut receipt = A2ATaskReceipt {
            receipt_type: "A2ATaskReceipt".to_string(),
            receipt_schema: "A2ATaskReceipt.v1".to_string(),
            hash_algorithm: "BLAKE3".to_string(),
            avatar: entry.avatar,
            jtbd: entry.jtbd,
            task_id: entry.task_id,
            status: task_state,
            mcp: McpInvocationEvidence {
                server: "ggen".to_string(),
                transport: "stdio".to_string(),
                tool_name: "ggen.construct".to_string(),
                tool_call_hash,
            },
            expected_path: ExpectedPathEvidence {
                route_id: "a2a.v1".to_string(),
                expected_ocel_hash: Some(expected_ocel.canonical_hash.clone()),
                expected_artifact: "artifact_needed.md".to_string(),
            },
            observed_path: ObservedPathEvidence {
                observed_ocel_hash: ocel.canonical_hash.clone(),
                ocel,
                observed_artifact_hash,
            },
            alignment: AlignmentEvidence {
                expected_vs_observed: if refusal.is_some() {
                    "Refused".to_string()
                } else {
                    "Aligned".to_string()
                },
                missing_events,
                unexpected_events: vec![],
                refusal_state: refusal.clone(),
            },
            refusal_state: refusal,
            previous_receipt_hash: None,
            receipt_hash: "".to_string(),
        };

        receipt.compute_and_set_hash();
        receipts.push(receipt);
    }

    if let Some(parent) = PathBuf::from(&out_path).parent() {
        fs::create_dir_all(parent).map_err(|e| {
            clap_noun_verb::NounVerbError::execution_error(format!(
                "Failed to create parent directory for receipt output: {}",
                e
            ))
        })?;
    }

    let output_json = serde_json::to_string_pretty(&receipts).map_err(|e| {
        clap_noun_verb::NounVerbError::execution_error(format!(
            "Failed to serialize receipts: {}",
            e
        ))
    })?;
    fs::write(&out_path, output_json).map_err(|e| {
        clap_noun_verb::NounVerbError::execution_error(format!(
            "Failed to write receipts file: {}",
            e
        ))
    })?;

    let ocel_trace = serde_json::json!({
        "event": "a2a.task.validated",
        "objects": {
            "receipt_count": receipts.len(),
            "verifier": "ggen a2a verify"
        }
    });
    log::info!("OCEL: {}", ocel_trace);

    Ok(VerifyOutput {
        receipt_chain: receipts,
        status: overall_status,
    })
}

/// Verify A2A tasks and emit receipts
#[verb]
fn verify(
    run: Option<String>, _expected: Option<String>, out: Option<String>,
) -> Result<VerifyOutput> {
    do_verify(run, out)
}

/// Prune all persisted A2A tasks (Eliminate Muda of Inventory)
#[verb]
fn prune() -> Result<serde_json::Value> {
    let task_dir = get_task_dir()?;
    let mut count = 0;

    if task_dir.exists() {
        for entry in fs::read_dir(&task_dir).map_err(|e| {
            clap_noun_verb::NounVerbError::execution_error(format!(
                "Failed to read task directory: {}",
                e
            ))
        })? {
            let entry = entry.map_err(|e| {
                clap_noun_verb::NounVerbError::execution_error(format!(
                    "Failed to read directory entry: {}",
                    e
                ))
            })?;
            let path = entry.path();
            if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("json") {
                fs::remove_file(path).map_err(|e| {
                    clap_noun_verb::NounVerbError::execution_error(format!(
                        "Failed to remove task file: {}",
                        e
                    ))
                })?;
                count += 1;
            }
        }
    }

    Ok(serde_json::json!({
        "status": "success",
        "tasks_pruned": count,
        "message": format!("Successfully pruned {} tasks", count)
    }))
}
