use anyhow::{anyhow, bail, Result};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use ulid::Ulid;

pub(crate) const RUNTIME_API_FAMILY: &str = "ccd-runtime";
pub(crate) const RUNTIME_API_VERSION: u32 = 1;
pub(crate) const EVALUATE_CONTEXT_REFRESH_OPERATION: &str = "evaluate_context_refresh";

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct RuntimeApiView {
    family: &'static str,
    version: u32,
    status: &'static str,
    discovery_command: &'static str,
    transport_support: RuntimeTransportSupportView,
    operations: Vec<RuntimeOperationView>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
struct RuntimeTransportSupportView {
    cli: RuntimeTransportView,
    mcp: RuntimeTransportView,
    library: RuntimeTransportView,
    daemon: RuntimeTransportView,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
struct RuntimeTransportView {
    status: &'static str,
    supports_events: bool,
    event_families: Vec<&'static str>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
struct RuntimeOperationView {
    name: &'static str,
    family: &'static str,
    status: &'static str,
    current_surfaces: Vec<&'static str>,
    canonical_owner: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    current_gap: Option<&'static str>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct RuntimeRequestMetadata {
    api_family: String,
    api_version: u32,
    request_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    actor_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    supervisor_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    host_session_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    host_run_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    host_task_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    approval_request_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    idempotency_key: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct RuntimeResponseMetadata {
    api_family: String,
    api_version: u32,
    operation: String,
    request_id: String,
    actor_id: Option<String>,
    supervisor_id: Option<String>,
    host_session_id: Option<String>,
    host_run_id: Option<String>,
    host_task_id: Option<String>,
    approval_request_id: Option<String>,
    idempotency_key: Option<String>,
    retry_class: &'static str,
}

pub(crate) fn describe_runtime_api() -> RuntimeApiView {
    #[cfg(feature = "daemon")]
    let daemon_transport = RuntimeTransportView {
        status: "available",
        supports_events: true,
        event_families: crate::runtime_events::advertised_families(),
    };
    #[cfg(not(feature = "daemon"))]
    let daemon_transport = RuntimeTransportView {
        status: "planned",
        supports_events: false,
        event_families: Vec::new(),
    };

    #[cfg(feature = "daemon")]
    let stream_runtime_events = RuntimeOperationView {
        name: "stream_runtime_events",
        family: "startup_and_inspection",
        status: "available",
        current_surfaces: vec!["daemon:/events"],
        canonical_owner: "kernel_runtime_state",
        current_gap: None,
    };
    #[cfg(not(feature = "daemon"))]
    let stream_runtime_events = RuntimeOperationView {
        name: "stream_runtime_events",
        family: "startup_and_inspection",
        status: "planned",
        current_surfaces: Vec::new(),
        canonical_owner: "kernel_runtime_state",
        current_gap: Some(
            "implementation removed — event streaming is planned for daemon transport",
        ),
    };

    RuntimeApiView {
        family: RUNTIME_API_FAMILY,
        version: RUNTIME_API_VERSION,
        status: "draft",
        discovery_command: "describe",
        transport_support: RuntimeTransportSupportView {
            cli: RuntimeTransportView {
                status: "available",
                supports_events: false,
                event_families: Vec::new(),
            },
            mcp: RuntimeTransportView {
                status: "available",
                supports_events: false,
                event_families: Vec::new(),
            },
            library: RuntimeTransportView {
                status: "available",
                supports_events: false,
                event_families: Vec::new(),
            },
            daemon: daemon_transport,
        },
        operations: vec![
            RuntimeOperationView {
                name: "describe_contract",
                family: "discovery",
                status: "available",
                current_surfaces: vec!["describe"],
                canonical_owner: "kernel_command_metadata",
                current_gap: None,
            },
            RuntimeOperationView {
                name: "describe_runtime_capabilities",
                family: "discovery",
                status: "available",
                current_surfaces: vec!["describe"],
                canonical_owner: "kernel_command_metadata",
                current_gap: None,
            },
            RuntimeOperationView {
                name: "describe_active_extensions",
                family: "discovery",
                status: "available",
                current_surfaces: vec!["describe", "start", "status", "runtime-state export"],
                canonical_owner: "typed_extension_envelopes",
                current_gap: None,
            },
            RuntimeOperationView {
                name: "load_startup_context",
                family: "startup_and_inspection",
                status: "partial",
                current_surfaces: vec![
                    "host-hook on-session-start",
                    "start",
                    "status",
                    "runtime-state export",
                    "mcp:ccd_session/start",
                ],
                canonical_owner: "kernel_startup_context",
                current_gap: Some(
                    "wrapper-first startup remains CLI-only; MCP exposes the lower-level context loader",
                ),
            },
            RuntimeOperationView {
                name: "load_runtime_status",
                family: "startup_and_inspection",
                status: "partial",
                current_surfaces: vec!["status", "runtime-state export", "doctor"],
                canonical_owner: "kernel_runtime_state",
                current_gap: Some("no one runtime-shaped status response exists yet"),
            },
            RuntimeOperationView {
                name: "export_projection",
                family: "startup_and_inspection",
                status: "available",
                current_surfaces: vec!["runtime-state export"],
                canonical_owner: "kernel_runtime_state",
                current_gap: Some("projection formats are still command-shaped"),
            },
            RuntimeOperationView {
                name: "load_child_bootstrap",
                family: "startup_and_inspection",
                status: "available",
                current_surfaces: vec![
                    "runtime-state child-bootstrap",
                    "mcp:ccd_delegation/child-bootstrap",
                ],
                canonical_owner: "kernel_startup_context",
                current_gap: Some(
                    "runtime still owns delegation lifecycle and isolated workspace provisioning",
                ),
            },
            stream_runtime_events,
            RuntimeOperationView {
                name: "start_session",
                family: "session_lifecycle",
                status: "available",
                current_surfaces: vec![
                    "session-state start",
                    "mcp:ccd_session_lifecycle/start-session",
                ],
                canonical_owner: "kernel_session_lifecycle",
                current_gap: Some("retry envelopes are not yet surfaced"),
            },
            RuntimeOperationView {
                name: "heartbeat_session",
                family: "session_lifecycle",
                status: "available",
                current_surfaces: vec![
                    "session-state heartbeat",
                    "mcp:ccd_session_lifecycle/heartbeat-session",
                ],
                canonical_owner: "kernel_session_lifecycle",
                current_gap: Some("retry envelopes are not yet surfaced"),
            },
            RuntimeOperationView {
                name: "clear_session",
                family: "session_lifecycle",
                status: "available",
                current_surfaces: vec![
                    "session-state clear",
                    "mcp:ccd_session_lifecycle/clear-session",
                ],
                canonical_owner: "kernel_session_lifecycle",
                current_gap: Some("idempotency and ownership semantics remain command-shaped"),
            },
            RuntimeOperationView {
                name: "takeover_session",
                family: "session_lifecycle",
                status: "available",
                current_surfaces: vec![
                    "session-state takeover",
                    "mcp:ccd_session_lifecycle/takeover-session",
                ],
                canonical_owner: "kernel_session_lifecycle",
                current_gap: Some("retry envelopes are not yet surfaced"),
            },
            RuntimeOperationView {
                name: "list_execution_gates",
                family: "protected_surfaces",
                status: "available",
                current_surfaces: vec![
                    "session-state gates list",
                    "mcp:ccd_session_gates/list-gates",
                ],
                canonical_owner: "kernel_execution_gates",
                current_gap: Some("the shared response envelope is not yet surfaced"),
            },
            RuntimeOperationView {
                name: "replace_execution_gates",
                family: "protected_surfaces",
                status: "available",
                current_surfaces: vec![
                    "session-state gates replace",
                    "mcp:ccd_session_gates/replace-gates",
                ],
                canonical_owner: "kernel_execution_gates",
                current_gap: Some("retry envelopes are not yet surfaced"),
            },
            RuntimeOperationView {
                name: "seed_execution_gates",
                family: "protected_surfaces",
                status: "available",
                current_surfaces: vec![
                    "session-state gates seed",
                    "mcp:ccd_session_gates/seed-gates",
                ],
                canonical_owner: "kernel_execution_gates",
                current_gap: Some("retry envelopes are not yet surfaced"),
            },
            RuntimeOperationView {
                name: "set_execution_gate_status",
                family: "protected_surfaces",
                status: "available",
                current_surfaces: vec![
                    "session-state gates set-status",
                    "mcp:ccd_session_gates/set-gate-status",
                ],
                canonical_owner: "kernel_execution_gates",
                current_gap: Some("retry envelopes are not yet surfaced"),
            },
            RuntimeOperationView {
                name: "advance_execution_gates",
                family: "protected_surfaces",
                status: "available",
                current_surfaces: vec![
                    "session-state gates advance",
                    "mcp:ccd_session_gates/advance-gates",
                ],
                canonical_owner: "kernel_execution_gates",
                current_gap: Some("retry envelopes are not yet surfaced"),
            },
            RuntimeOperationView {
                name: "clear_execution_gates",
                family: "protected_surfaces",
                status: "available",
                current_surfaces: vec![
                    "session-state gates clear",
                    "mcp:ccd_session_gates/clear-gates",
                ],
                canonical_owner: "kernel_execution_gates",
                current_gap: Some("retry envelopes are not yet surfaced"),
            },
            RuntimeOperationView {
                name: "refresh_handoff_view",
                family: "continuity_and_recovery",
                status: "partial",
                current_surfaces: vec!["handoff refresh", "start --refresh"],
                canonical_owner: "kernel_continuity",
                current_gap: Some("refresh semantics remain split across surfaces"),
            },
            RuntimeOperationView {
                name: "write_handoff",
                family: "continuity_and_recovery",
                status: "partial",
                current_surfaces: vec!["handoff write", "handover", "checkpoint"],
                canonical_owner: "kernel_continuity",
                current_gap: Some("overwrite and retry posture remain command-shaped"),
            },
            RuntimeOperationView {
                name: "write_recovery",
                family: "continuity_and_recovery",
                status: "available",
                current_surfaces: vec!["recovery write", "mcp:ccd_recovery/write-recovery"],
                canonical_owner: "kernel_recovery_artifact",
                current_gap: Some("CLI, MCP, and daemon event adapters remain follow-on"),
            },
            RuntimeOperationView {
                name: "evaluate_checkpoint",
                family: "evaluation",
                status: "partial",
                current_surfaces: vec!["handover", "checkpoint"],
                canonical_owner: "kernel_runtime_evaluation",
                current_gap: Some("pure evaluation and conditional write remain coupled"),
            },
            RuntimeOperationView {
                name: EVALUATE_CONTEXT_REFRESH_OPERATION,
                family: "evaluation",
                status: "available",
                current_surfaces: vec!["context-check", "mcp:ccd_context/context-check"],
                canonical_owner: "kernel_runtime_evaluation",
                current_gap: Some("runtime reminder rendering remains external"),
            },
            RuntimeOperationView {
                name: "evaluate_closeout",
                family: "evaluation",
                status: "partial",
                current_surfaces: vec!["handover"],
                canonical_owner: "kernel_runtime_evaluation",
                current_gap: Some("no explicit close-out operation response exists yet"),
            },
            RuntimeOperationView {
                name: "list_escalations",
                family: "escalation",
                status: "available",
                current_surfaces: vec![
                    "escalation-state list",
                    "mcp:ccd_escalation/list-escalations",
                    "mcp:ccd_state/escalation-state-list",
                ],
                canonical_owner: "kernel_escalation_state",
                current_gap: Some("legacy aliases still exist on `ccd_state`"),
            },
            RuntimeOperationView {
                name: "set_escalation",
                family: "escalation",
                status: "available",
                current_surfaces: vec![
                    "escalation-state set",
                    "mcp:ccd_escalation/set-escalation",
                    "mcp:ccd_state/escalation-state-set",
                ],
                canonical_owner: "kernel_escalation_state",
                current_gap: Some("approval-resolution events are not yet surfaced"),
            },
            RuntimeOperationView {
                name: "clear_escalation",
                family: "escalation",
                status: "available",
                current_surfaces: vec![
                    "escalation-state clear",
                    "mcp:ccd_escalation/clear-escalation",
                ],
                canonical_owner: "kernel_escalation_state",
                current_gap: Some("approval-resolution events are not yet surfaced"),
            },
            RuntimeOperationView {
                name: "submit_memory_evidence",
                family: "memory_follow_through",
                status: "available",
                current_surfaces: vec![
                    "memory evidence submit",
                    "mcp:ccd_memory_capture/memory-evidence-submit",
                ],
                canonical_owner: "kernel_governed_memory",
                current_gap: Some("runtime adapters still own raw transcript and prompt history"),
            },
            RuntimeOperationView {
                name: "extract_memory_candidates",
                family: "memory_follow_through",
                status: "available",
                current_surfaces: vec![
                    "memory candidate extract",
                    "mcp:ccd_memory_capture/memory-candidate-extract",
                ],
                canonical_owner: "kernel_governed_memory",
                current_gap: Some(
                    "broader-scope evidence remains staged until later automation phases",
                ),
            },
            RuntimeOperationView {
                name: "probe_ingest_sync_status",
                family: "memory_follow_through",
                status: "available",
                current_surfaces: vec![
                    "memory sync-status",
                    "mcp:ccd_memory_recall/memory-sync-status",
                ],
                canonical_owner: "kernel_governed_memory",
                current_gap: Some("raw transcript and event ingestion remain provider-owned"),
            },
            RuntimeOperationView {
                name: "export_ingest_source_map",
                family: "memory_follow_through",
                status: "available",
                current_surfaces: vec![
                    "memory source-map",
                    "mcp:ccd_memory_recall/memory-source-map",
                ],
                canonical_owner: "kernel_governed_memory",
                current_gap: Some("raw transcript and event ingestion remain provider-owned"),
            },
            RuntimeOperationView {
                name: "admit_memory_candidate",
                family: "memory_follow_through",
                status: "available",
                current_surfaces: vec![
                    "memory candidate admit",
                    "mcp:ccd_memory/memory-candidate-admit",
                ],
                canonical_owner: "kernel_governed_memory",
                current_gap: Some(
                    "the shared runtime request/response envelope is not exposed yet",
                ),
            },
            RuntimeOperationView {
                name: "promote_memory",
                family: "memory_follow_through",
                status: "available",
                current_surfaces: vec!["memory promote"],
                canonical_owner: "kernel_governed_memory",
                current_gap: Some("the shared conflict and idempotency model is not exposed yet"),
            },
            RuntimeOperationView {
                name: "compact_memory",
                family: "memory_follow_through",
                status: "available",
                current_surfaces: vec!["memory compact"],
                canonical_owner: "kernel_governed_memory",
                current_gap: Some("event metadata are not yet surfaced"),
            },
        ],
    }
}

pub(crate) fn parse_runtime_request(args: &Value) -> Result<Option<RuntimeRequestMetadata>> {
    let Some(request) = args.get("runtime_request") else {
        return Ok(None);
    };
    let request = request
        .as_object()
        .ok_or_else(|| anyhow!("invalid argument runtime_request: expected an object"))?;
    let api_family = request
        .get("api_family")
        .ok_or_else(|| anyhow!("missing required argument: runtime_request.api_family"))?
        .as_str()
        .ok_or_else(|| anyhow!("invalid argument runtime_request.api_family: expected a string"))?;
    if api_family != RUNTIME_API_FAMILY {
        bail!("invalid runtime_request.api_family: expected {RUNTIME_API_FAMILY}");
    }
    let api_version = request
        .get("api_version")
        .ok_or_else(|| anyhow!("missing required argument: runtime_request.api_version"))?
        .as_u64()
        .ok_or_else(|| {
            anyhow!("invalid argument runtime_request.api_version: expected an unsigned integer")
        })?;
    if api_version != RUNTIME_API_VERSION as u64 {
        bail!("invalid runtime_request.api_version: expected {RUNTIME_API_VERSION}");
    }
    let request_id = request
        .get("request_id")
        .and_then(Value::as_str)
        .ok_or_else(|| anyhow!("missing required argument: runtime_request.request_id"))?;

    Ok(Some(RuntimeRequestMetadata {
        api_family: api_family.to_owned(),
        api_version: api_version as u32,
        request_id: request_id.to_owned(),
        actor_id: opt_object_string(request, "actor_id"),
        supervisor_id: opt_object_string(request, "supervisor_id"),
        host_session_id: opt_object_string(request, "host_session_id"),
        host_run_id: opt_object_string(request, "host_run_id"),
        host_task_id: opt_object_string(request, "host_task_id"),
        approval_request_id: opt_object_string(request, "approval_request_id"),
        idempotency_key: opt_object_string(request, "idempotency_key"),
    }))
}

pub(crate) fn runtime_retry_class(operation: &str) -> &'static str {
    match operation {
        EVALUATE_CONTEXT_REFRESH_OPERATION
        | "load_startup_context"
        | "load_runtime_status"
        | "export_projection"
        | "heartbeat_session" => "safe_retry",
        "takeover_session" => "do_not_retry",
        "start_session"
        | "clear_session"
        | "replace_execution_gates"
        | "seed_execution_gates"
        | "set_execution_gate_status"
        | "advance_execution_gates"
        | "clear_execution_gates"
        | "refresh_handoff_view"
        | "write_handoff"
        | "write_recovery"
        | "evaluate_checkpoint"
        | "evaluate_closeout"
        | "set_escalation"
        | "clear_escalation"
        | "submit_memory_evidence"
        | "extract_memory_candidates"
        | "probe_ingest_sync_status"
        | "export_ingest_source_map"
        | "admit_memory_candidate"
        | "promote_memory"
        | "compact_memory" => "retry_after_reread",
        _ => "safe_retry",
    }
}

pub(crate) fn synthetic_response_metadata(operation: &str) -> RuntimeResponseMetadata {
    RuntimeResponseMetadata {
        api_family: RUNTIME_API_FAMILY.to_owned(),
        api_version: RUNTIME_API_VERSION,
        operation: operation.to_owned(),
        request_id: format!("evt_{}", Ulid::new()),
        actor_id: None,
        supervisor_id: None,
        host_session_id: None,
        host_run_id: None,
        host_task_id: None,
        approval_request_id: None,
        idempotency_key: None,
        retry_class: runtime_retry_class(operation),
    }
}

fn opt_object_string(request: &serde_json::Map<String, Value>, key: &str) -> Option<String> {
    request.get(key).and_then(Value::as_str).map(str::to_owned)
}

impl RuntimeRequestMetadata {
    pub(crate) fn response_metadata(
        &self,
        operation: &str,
        retry_class: &'static str,
    ) -> RuntimeResponseMetadata {
        RuntimeResponseMetadata {
            api_family: self.api_family.clone(),
            api_version: self.api_version,
            operation: operation.to_owned(),
            request_id: self.request_id.clone(),
            actor_id: self.actor_id.clone(),
            supervisor_id: self.supervisor_id.clone(),
            host_session_id: self.host_session_id.clone(),
            host_run_id: self.host_run_id.clone(),
            host_task_id: self.host_task_id.clone(),
            approval_request_id: self.approval_request_id.clone(),
            idempotency_key: self.idempotency_key.clone(),
            retry_class,
        }
    }
}

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

    #[test]
    fn runtime_manifest_reports_daemon_event_support_truthfully() {
        let manifest = describe_runtime_api();
        let stream = manifest
            .operations
            .iter()
            .find(|operation| operation.name == "stream_runtime_events")
            .expect("stream runtime events operation");

        assert_eq!(manifest.transport_support.library.status, "available");
        assert!(!manifest.transport_support.library.supports_events);
        assert!(manifest.transport_support.library.event_families.is_empty());
        if cfg!(feature = "daemon") {
            assert_eq!(manifest.transport_support.daemon.status, "available");
            assert!(manifest.transport_support.daemon.supports_events);
            assert_eq!(
                manifest.transport_support.daemon.event_families,
                vec![
                    "runtime.status",
                    "runtime.approval_required",
                    "runtime.checkpoint",
                    "runtime.memory_compaction"
                ]
            );
            assert_eq!(stream.status, "available");
            assert_eq!(stream.current_surfaces, vec!["daemon:/events"]);
        } else {
            assert_eq!(manifest.transport_support.daemon.status, "planned");
            assert!(!manifest.transport_support.daemon.supports_events);
            assert!(manifest.transport_support.daemon.event_families.is_empty());
            assert_eq!(stream.status, "planned");
            assert!(stream.current_surfaces.is_empty());
        }
    }

    #[test]
    fn runtime_manifest_reports_memory_evidence_operations() {
        let manifest = describe_runtime_api();
        let operation_names = manifest
            .operations
            .iter()
            .map(|operation| operation.name)
            .collect::<Vec<_>>();
        assert!(operation_names.contains(&"submit_memory_evidence"));
        assert!(operation_names.contains(&"extract_memory_candidates"));
        assert!(operation_names.contains(&"probe_ingest_sync_status"));
        assert!(operation_names.contains(&"export_ingest_source_map"));
    }

    #[test]
    fn parse_runtime_request_requires_api_identity_fields() {
        let missing_family = json!({
            "runtime_request": {
                "api_version": RUNTIME_API_VERSION,
                "request_id": "req-runtime-stream"
            }
        });
        let error = parse_runtime_request(&missing_family).expect_err("missing api_family");
        assert_eq!(
            error.to_string(),
            "missing required argument: runtime_request.api_family"
        );

        let missing_version = json!({
            "runtime_request": {
                "api_family": RUNTIME_API_FAMILY,
                "request_id": "req-runtime-stream"
            }
        });
        let error = parse_runtime_request(&missing_version).expect_err("missing api_version");
        assert_eq!(
            error.to_string(),
            "missing required argument: runtime_request.api_version"
        );
    }

    #[test]
    fn response_metadata_serializes_optional_fields_as_null() {
        let request = RuntimeRequestMetadata {
            api_family: RUNTIME_API_FAMILY.to_owned(),
            api_version: RUNTIME_API_VERSION,
            request_id: "req-runtime-stream".to_owned(),
            actor_id: None,
            supervisor_id: None,
            host_session_id: None,
            host_run_id: None,
            host_task_id: None,
            approval_request_id: None,
            idempotency_key: None,
        };

        let value = serde_json::to_value(request.response_metadata(
            EVALUATE_CONTEXT_REFRESH_OPERATION,
            runtime_retry_class(EVALUATE_CONTEXT_REFRESH_OPERATION),
        ))
        .expect("serialize response metadata");

        assert!(value.get("actor_id").is_some());
        assert!(value["actor_id"].is_null());
        assert!(value.get("supervisor_id").is_some());
        assert!(value["supervisor_id"].is_null());
        assert!(value.get("host_session_id").is_some());
        assert!(value["host_session_id"].is_null());
        assert!(value.get("host_run_id").is_some());
        assert!(value["host_run_id"].is_null());
        assert!(value.get("host_task_id").is_some());
        assert!(value["host_task_id"].is_null());
        assert!(value.get("approval_request_id").is_some());
        assert!(value["approval_request_id"].is_null());
        assert!(value.get("idempotency_key").is_some());
        assert!(value["idempotency_key"].is_null());
    }
}
