//! A2A v1.0 wire types — `Part`, `Message`, `Artifact`, `Task`, `AgentCard`.
//!
//! Field names are verbatim from the A2A v1.0 spec §6 to preserve
//! wire-compatibility with non-NATS A2A clients. See
//! <https://a2a-protocol.org/latest/specification/>.

#![allow(non_snake_case)] // Spec field names are camelCase.

use serde::{Deserialize, Serialize};

/// Author role of a message — A2A spec §6.3.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Role {
    /// Sent by the human / calling system.
    User,
    /// Sent by the agent.
    Agent,
}

/// Lifecycle states of a long-running task — A2A spec §6.5.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TaskStatus {
    /// Task accepted, not yet started.
    Submitted,
    /// Task is executing.
    Working,
    /// Task is paused awaiting more user input.
    InputRequired,
    /// Task succeeded.
    Completed,
    /// Task ended with an error.
    Failed,
    /// Task was cancelled by the caller.
    Canceled,
    /// Task was rejected by the server before starting.
    Rejected,
    /// Task is waiting on authentication.
    Authenticated,
}

impl TaskStatus {
    /// `true` if this status cannot transition further. Stream
    /// consumers close after observing a terminal status.
    pub fn is_terminal(&self) -> bool {
        matches!(
            self,
            TaskStatus::Completed
                | TaskStatus::Failed
                | TaskStatus::Canceled
                | TaskStatus::Rejected
        )
    }
}

/// One element of a multi-part payload — A2A spec §6.2.
///
/// Tagged enum on `"type"`. Optional `metadata`, `filename`, and
/// `mediaType` are present on every variant per the spec.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum Part {
    /// UTF-8 text part.
    Text {
        /// The text body.
        content: String,
        /// Free-form metadata attached to the part.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        metadata: Option<serde_json::Value>,
        /// IANA media type hint (rarely used for text).
        #[serde(default, skip_serializing_if = "Option::is_none")]
        mediaType: Option<String>,
        /// Suggested filename when extracting the part.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        filename: Option<String>,
    },
    /// Inline binary blob, base64-encoded.
    Raw {
        /// Base64-encoded bytes.
        bytes: String,
        /// IANA media type of the bytes.
        mediaType: String,
        /// Free-form metadata.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        metadata: Option<serde_json::Value>,
        /// Suggested filename.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        filename: Option<String>,
    },
    /// External resource reference.
    Url {
        /// URL to fetch.
        url: String,
        /// IANA media type hint.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        mediaType: Option<String>,
        /// Free-form metadata.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        metadata: Option<serde_json::Value>,
        /// Suggested filename.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        filename: Option<String>,
    },
    /// Arbitrary structured JSON.
    Data {
        /// The JSON value.
        data: serde_json::Value,
        /// Free-form metadata.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        metadata: Option<serde_json::Value>,
        /// IANA media type hint (rarely used).
        #[serde(default, skip_serializing_if = "Option::is_none")]
        mediaType: Option<String>,
        /// Suggested filename.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        filename: Option<String>,
    },
}

/// One message exchanged between user and agent — A2A spec §6.3.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
    /// Stable id for this message.
    pub messageId: String,
    /// Conversation/context id grouping related messages.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub contextId: Option<String>,
    /// Optional task id this message participates in.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub taskId: Option<String>,
    /// Author of the message.
    pub role: Role,
    /// Parts that make up the message.
    pub parts: Vec<Part>,
    /// Free-form metadata.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub metadata: Option<serde_json::Value>,
    /// Extension URIs the message uses.
    #[serde(default)]
    pub extensions: Vec<String>,
    /// Tasks this message references but does not own.
    #[serde(default)]
    pub referenceTaskIds: Vec<String>,
}

/// Output produced by an agent run — A2A spec §6.4.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Artifact {
    /// Stable id for this artifact.
    pub artifactId: String,
    /// Optional human-readable name.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    /// Optional description.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    /// Parts that make up the artifact.
    pub parts: Vec<Part>,
    /// Free-form metadata.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub metadata: Option<serde_json::Value>,
    /// Extension URIs the artifact uses.
    #[serde(default)]
    pub extensions: Vec<String>,
}

/// Long-running task — A2A spec §6.5.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
    /// Stable id for this task.
    pub id: String,
    /// Conversation/context id this task belongs to.
    pub contextId: String,
    /// Lifecycle status.
    pub status: TaskStatus,
    /// Artifacts produced by the task.
    #[serde(default)]
    pub artifacts: Vec<Artifact>,
    /// Message history of the task.
    #[serde(default)]
    pub history: Vec<Message>,
    /// Free-form metadata.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub metadata: Option<serde_json::Value>,
}

/// Provider information for an agent — A2A spec §6.1.2.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentProvider {
    /// Organisation name.
    pub organization: String,
    /// Optional contact URL.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
}

/// Capabilities the agent supports — A2A spec §6.1.3.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentCapabilities {
    /// Supports `SendStreamingMessage`.
    #[serde(default)]
    pub streaming: bool,
    /// Supports push-notification config ops.
    #[serde(default)]
    pub pushNotifications: bool,
    /// Records state transitions in task history.
    #[serde(default)]
    pub stateTransitionHistory: bool,
}

/// Skill an agent advertises — A2A spec §6.1.4.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentSkill {
    /// Stable skill id.
    pub id: String,
    /// Human-readable name.
    pub name: String,
    /// Free-form description.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    /// Tags for discovery.
    #[serde(default)]
    pub tags: Vec<String>,
}

/// Transport interface an agent exposes — A2A spec §6.1.5.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentInterface {
    /// Transport identifier (e.g. `"a2a/1.0/jsonrpc"` or klieo-specific
    /// `"a2a/1.0/nats"`).
    pub transport: String,
    /// URL or NATS subject prefix the transport listens on.
    pub url: String,
}

/// Detached signature for an `AgentCard` — A2A spec §6.1.7.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentCardSignature {
    /// Algorithm identifier (e.g. `"ed25519"`).
    pub alg: String,
    /// Key id used.
    pub kid: String,
    /// Base64-encoded signature bytes.
    pub signature: String,
}

/// Public description of an agent — A2A spec §6.1.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentCard {
    /// Stable agent id.
    pub id: String,
    /// Display name.
    pub name: String,
    /// Free-form description.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    /// Provider org info.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub provider: Option<AgentProvider>,
    /// Supported capabilities.
    pub capabilities: AgentCapabilities,
    /// Advertised skills.
    #[serde(default)]
    pub skills: Vec<AgentSkill>,
    /// Transport interfaces.
    #[serde(default)]
    pub interfaces: Vec<AgentInterface>,
    /// Security schemes (OpenAPI-style).
    #[serde(default)]
    pub securitySchemes: serde_json::Map<String, serde_json::Value>,
    /// Required security scheme references.
    #[serde(default)]
    pub security: Vec<serde_json::Value>,
    /// Extension URIs the agent uses.
    #[serde(default)]
    pub extensions: Vec<String>,
    /// Optional detached signature.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub signature: Option<AgentCardSignature>,
}

/// `SendMessage` params — A2A spec §9.4.1.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SendMessageParams {
    /// The message to send.
    pub message: Message,
    /// Optional configuration knobs (per-call timeout, etc).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub configuration: Option<serde_json::Value>,
}

/// `SendMessage` result.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SendMessageResult {
    /// Server completed synchronously and returned a response message.
    Message(Message),
    /// Server kicked off a long-running task.
    Task(Task),
}

/// `GetTask` params — A2A spec §9.4.3.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetTaskParams {
    /// Task id to fetch.
    pub id: String,
    /// Optional history truncation depth.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub historyLength: Option<usize>,
}

/// `ListTasks` params — A2A spec §9.4.4.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListTasksParams {
    /// Optional context filter.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub contextId: Option<String>,
    /// Optional pagination cursor.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub cursor: Option<String>,
    /// Optional page size.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub limit: Option<usize>,
}

/// `ListTasks` result.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListTasksResult {
    /// Page of tasks.
    pub tasks: Vec<Task>,
    /// Cursor to use for the next page, if more remain.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub nextCursor: Option<String>,
}

/// `CancelTask` params — A2A spec §9.4.5.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CancelTaskParams {
    /// Task id to cancel.
    pub id: String,
}

/// `SubscribeToTask` params — A2A spec §9.4.6.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscribeToTaskParams {
    /// Task id to subscribe to.
    pub id: String,
}

/// `CreateTaskPushNotificationConfig` params — A2A spec §9.4.7.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PushNotificationConfigParams {
    /// Task id.
    pub taskId: String,
    /// Webhook URL.
    pub url: String,
    /// Optional bearer token.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub token: Option<String>,
}

/// Stored push-notification config.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PushNotificationConfig {
    /// Stable config id.
    pub id: String,
    /// Owning task id.
    pub taskId: String,
    /// Webhook URL.
    pub url: String,
}

/// `GetTaskPushNotificationConfig` params — A2A spec §9.4.8.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetPushNotificationConfigParams {
    /// Task id.
    pub taskId: String,
    /// Config id.
    pub id: String,
}

/// `ListTaskPushNotificationConfigs` params — A2A spec §9.4.9.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListPushNotificationConfigsParams {
    /// Task id.
    pub taskId: String,
}

/// `ListTaskPushNotificationConfigs` result.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListPushNotificationConfigsResult {
    /// Stored configs for the task.
    pub configs: Vec<PushNotificationConfig>,
}

/// `DeleteTaskPushNotificationConfig` params — A2A spec §9.4.10.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeletePushNotificationConfigParams {
    /// Task id.
    pub taskId: String,
    /// Config id.
    pub id: String,
}

/// `GetExtendedAgentCard` params — A2A spec §9.4.11. Intentionally empty per spec.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GetExtendedAgentCardParams {}

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

    #[test]
    fn part_text_serialises_with_type_text() {
        let p = Part::Text {
            content: "hello".into(),
            metadata: None,
            mediaType: None,
            filename: None,
        };
        let v = serde_json::to_value(&p).unwrap();
        assert_eq!(v["type"], "text");
        assert_eq!(v["content"], "hello");
    }

    #[test]
    fn part_raw_uses_base64_string_field() {
        let p = Part::Raw {
            bytes: "aGVsbG8=".into(),
            mediaType: "text/plain".into(),
            metadata: None,
            filename: None,
        };
        let v = serde_json::to_value(&p).unwrap();
        assert_eq!(v["type"], "raw");
        assert_eq!(v["bytes"], "aGVsbG8=");
        assert_eq!(v["mediaType"], "text/plain");
    }

    #[test]
    fn message_round_trips() {
        let m = Message {
            messageId: "m-1".into(),
            contextId: Some("c-1".into()),
            taskId: Some("t-1".into()),
            role: Role::User,
            parts: vec![Part::Text {
                content: "hi".into(),
                metadata: None,
                mediaType: None,
                filename: None,
            }],
            metadata: None,
            extensions: vec![],
            referenceTaskIds: vec![],
        };
        let v = serde_json::to_value(&m).unwrap();
        let back: Message = serde_json::from_value(v).unwrap();
        assert_eq!(back.messageId, "m-1");
        assert_eq!(back.role, Role::User);
        assert_eq!(back.parts.len(), 1);
    }

    #[test]
    fn task_with_empty_history_serialises_minimally() {
        let t = Task {
            id: "t-1".into(),
            contextId: "c-1".into(),
            status: TaskStatus::Submitted,
            artifacts: vec![],
            history: vec![],
            metadata: None,
        };
        let v = serde_json::to_value(&t).unwrap();
        assert_eq!(v["id"], "t-1");
        assert_eq!(v["status"], "submitted");
    }

    #[test]
    fn agent_card_round_trips() {
        let card = AgentCard {
            id: "agent-1".into(),
            name: "Echo".into(),
            description: Some("Echoes input".into()),
            provider: None,
            capabilities: AgentCapabilities {
                streaming: false,
                pushNotifications: false,
                stateTransitionHistory: false,
            },
            skills: vec![],
            interfaces: vec![],
            securitySchemes: serde_json::Map::new(),
            security: vec![],
            extensions: vec![],
            signature: None,
        };
        let v = serde_json::to_value(&card).unwrap();
        let back: AgentCard = serde_json::from_value(v).unwrap();
        assert_eq!(back.id, "agent-1");
    }

    #[test]
    fn role_agent_serialises_as_lowercase() {
        let v = serde_json::to_value(Role::Agent).unwrap();
        assert_eq!(v, json!("agent"));
    }

    #[test]
    fn task_status_is_terminal_covers_completed_failed_canceled_rejected() {
        assert!(TaskStatus::Completed.is_terminal());
        assert!(TaskStatus::Failed.is_terminal());
        assert!(TaskStatus::Canceled.is_terminal());
        assert!(TaskStatus::Rejected.is_terminal());
    }

    #[test]
    fn task_status_is_terminal_excludes_in_progress() {
        assert!(!TaskStatus::Submitted.is_terminal());
        assert!(!TaskStatus::Working.is_terminal());
        assert!(!TaskStatus::Authenticated.is_terminal());
        assert!(!TaskStatus::InputRequired.is_terminal());
    }
}
