//! A2A v1.0 protocol types.
//!
//! Wire field names follow the published spec at
//! <https://a2a-protocol.org/latest/specification/>. Rust identifiers stay
//! `snake_case`; `#[serde(rename_all = "camelCase")]` handles the
//! translation. Optional fields use `Option<T>` with `default` so peer
//! payloads that omit them parse cleanly.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;

// --- Agent Card -------------------------------------------------------------

/// A2A Agent Card. Served at `/.well-known/agent-card.json` by an
/// A2A agent so peers can discover capabilities, skills, and security
/// schemes before sending their first task.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentCard {
    pub name: String,
    pub description: String,
    /// Main URL where the agent is reachable. The transport at this
    /// URL is `preferred_transport`.
    pub url: String,
    /// The agent's product version (not the protocol version — that
    /// goes in `protocol_version`).
    pub version: String,
    /// A2A protocol version the agent speaks.
    pub protocol_version: String,
    /// Transport at the main `url`. Defaults to "JSONRPC".
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub preferred_transport: Option<String>,
    /// Optional URL to additional documentation about the agent.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub documentation_url: Option<String>,
    /// Optional URL to an icon image for UI surfaces.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub icon_url: Option<String>,
    pub provider: AgentProvider,
    pub capabilities: AgentCapabilities,
    pub default_input_modes: Vec<String>,
    pub default_output_modes: Vec<String>,
    pub skills: Vec<AgentSkill>,
    /// v1.0 canonical interface list. REQUIRED in v1.0; first entry
    /// is the preferred binding. The bridge populates this from the
    /// embedder's bind URL + protocol so v1.0-strict peers find
    /// reachability data here.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub supported_interfaces: Vec<AgentInterface>,
    /// v0.3 alias of `supported_interfaces` (without the first /
    /// preferred entry, by convention). Kept on emission so v0.3
    /// peers reading the older field name still find transports.
    /// New code sets `supported_interfaces`; this is derived for
    /// backward compat.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub additional_interfaces: Vec<AgentInterface>,
    /// Available security schemes the agent advertises (by name).
    /// `security_requirements` (below) lists which combinations are
    /// *required* per request.
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub security_schemes: HashMap<String, SecurityScheme>,
    /// Per-request security requirements: a list of `SecurityRequirement`
    /// objects per A2A v1.0. The outer list is alternatives (OR);
    /// each `SecurityRequirement.schemes` map names the schemes that
    /// must all be present (AND). Empty list = no auth required.
    ///
    /// Wire form: `[{"schemes": {"oauth": {"list": ["read"]}}},
    ///              {"schemes": {"api-key": {"list": []},
    ///                           "mtls": {"list": []}}}]`
    /// — "OAuth(read) OR (api-key AND mtls)."
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub security_requirements: Vec<SecurityRequirement>,
    /// True if the agent serves an extended card via
    /// `GetExtendedAgentCard` (v1.0) /
    /// `agent/getAuthenticatedExtendedCard` (v0.3) once authenticated.
    #[serde(default)]
    pub supports_authenticated_extended_card: bool,
    /// JWS signatures computed for this `AgentCard` per RFC 7515.
    /// Populated by [`crate::signing::sign_agent_card`] when the
    /// embedder wants peers to verify provenance / domain. Empty
    /// vector when the card is unsigned.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub signatures: Vec<AgentCardSignature>,
}

/// One JWS signature attached to an `AgentCard`. Follows the JSON
/// serialization of RFC 7515 in detached form — the AgentCard JSON
/// itself is the payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentCardSignature {
    /// The protected JWS header. Base64url-encoded JSON object.
    pub protected: String,
    /// The computed signature, base64url-encoded.
    pub signature: String,
    /// Optional unprotected JWS header values.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub header: Option<Value>,
}

impl Default for AgentCard {
    /// Test-friendly defaults. Fills in every required field with a
    /// placeholder so `AgentCard { name: "...", url: "...", ..Default::default() }`
    /// works for tests and embedders that want to mutate just a few
    /// fields. Not meant for production cards — for those, use
    /// [`crate::card::build_default_agent_card`] which derives skills
    /// from a live runtime.
    fn default() -> Self {
        Self {
            name: String::new(),
            description: String::new(),
            url: String::new(),
            version: "1.0.0".into(),
            protocol_version: "1.0".into(),
            preferred_transport: Some("JSONRPC".into()),
            documentation_url: None,
            icon_url: None,
            provider: AgentProvider {
                organization: String::new(),
                url: None,
            },
            capabilities: AgentCapabilities::default(),
            default_input_modes: Vec::new(),
            default_output_modes: Vec::new(),
            skills: Vec::new(),
            supported_interfaces: Vec::new(),
            additional_interfaces: Vec::new(),
            security_schemes: HashMap::new(),
            security_requirements: Vec::new(),
            supports_authenticated_extended_card: false,
            signatures: Vec::new(),
        }
    }
}

/// A2A v1.0 `SecurityRequirement` — a single AND-group of schemes
/// that must all be present on a request. The outer `Vec` on
/// `AgentCard.security_requirements` is the OR alternatives.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SecurityRequirement {
    /// Map of scheme name → required scopes (wrapped in `StringList`
    /// per the proto's `map<string, StringList>` definition).
    #[serde(default)]
    pub schemes: HashMap<String, StringList>,
}

/// A2A v1.0 `StringList` — proto wrapper around a list of strings,
/// used as the value type in `SecurityRequirement.schemes` so the
/// JSON wire form is `{"list": [...]}` rather than a bare array.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StringList {
    #[serde(default)]
    pub list: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentProvider {
    pub organization: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentCapabilities {
    pub streaming: bool,
    pub push_notifications: bool,
    /// v0.3 holdover. v1.0 has no equivalent on `AgentCapabilities`;
    /// kept here for backward-compat emission.
    pub state_transition_history: bool,
    /// v1.0: indicates the agent serves an extended card via
    /// `GetExtendedAgentCard` once authenticated. The bridge mirrors
    /// this onto the v0.3 top-level `supportsAuthenticatedExtendedCard`
    /// for cross-version compat.
    #[serde(default)]
    pub extended_agent_card: bool,
    /// v1.0: protocol extensions the agent advertises. Empty by
    /// default. Modeled as `Value` to keep the wire form open while
    /// the spec settles.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub extensions: Vec<Value>,
}

/// One advertised skill on the agent card. CAR maps each registered
/// tool to a skill so peer agents can see what operations are
/// available.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentSkill {
    pub id: String,
    pub name: String,
    pub description: String,
    pub tags: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub examples: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub input_modes: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub output_modes: Vec<String>,
}

/// One supported transport interface an agent serves.
///
/// v1.0 fields per `a2a.proto::AgentInterface`:
/// - `url` — required absolute URL where this binding listens.
/// - `protocol_binding` — open string (`JSONRPC`, `GRPC`,
///   `HTTP+JSON`, or any future extension). Required.
/// - `tenant` — optional tenant id for multi-tenant deployments.
/// - `protocol_version` — required version string (`"0.3"`, `"1.0"`).
///
/// The bridge also serves a v0.3 alias `transport: TransportProtocol`
/// that mirrors `protocol_binding` for peers reading the older
/// schema. New embedders set `protocol_binding` directly and ignore
/// the alias.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentInterface {
    pub url: String,
    /// v1.0 wire field. Open string. The `TransportProtocol` constants
    /// (`"JSONRPC"`, `"GRPC"`, `"HTTP+JSON"`) are convenience values.
    #[serde(default = "default_protocol_binding")]
    pub protocol_binding: String,
    /// v0.3 alias of `protocol_binding`. Emitted alongside on the
    /// wire so v0.3-only peers can still read the transport. Strict
    /// v1.0 readers ignore this field.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub transport: Option<TransportProtocol>,
    /// Optional tenant id (multi-tenant deployments). v1.0 only.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tenant: Option<String>,
    /// A2A protocol version this interface speaks. Required in v1.0.
    /// Defaults to `"1.0"` when omitted.
    #[serde(default = "default_interface_protocol_version")]
    pub protocol_version: String,
}

fn default_protocol_binding() -> String {
    "JSONRPC".to_string()
}

fn default_interface_protocol_version() -> String {
    "1.0".to_string()
}

/// v0.3-style transport enum — kept for ergonomics. v1.0 uses
/// `AgentInterface.protocol_binding: String` (open). New code should
/// prefer the string field; this enum exists to ease migration.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum TransportProtocol {
    #[serde(rename = "JSONRPC")]
    JsonRpc,
    #[serde(rename = "GRPC")]
    Grpc,
    #[serde(rename = "HTTP+JSON")]
    HttpJson,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum SecurityScheme {
    #[serde(rename = "apiKey")]
    ApiKey {
        #[serde(rename = "in")]
        location: String,
        name: String,
    },
    #[serde(rename = "http")]
    Http { scheme: String },
    #[serde(rename = "oauth2")]
    OAuth2 { flows: Value },
    #[serde(rename = "openIdConnect")]
    OpenIdConnect {
        #[serde(rename = "openIdConnectUrl")]
        open_id_connect_url: String,
    },
    #[serde(rename = "mutualTLS")]
    MutualTls,
}

// --- Task / Message / Part / Artifact --------------------------------------

/// A2A Task lifecycle state.
///
/// Wire form is lowercase / kebab-case per the v1.0 JSON-RPC binding
/// (`"submitted"`, `"input-required"`, etc.). The protobuf definition
/// uses `TASK_STATE_*` enum names — that surface is gRPC-only and
/// must not appear on JSON wire.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum TaskState {
    Submitted,
    Working,
    InputRequired,
    AuthRequired,
    Completed,
    Failed,
    Canceled,
    Rejected,
}

impl TaskState {
    pub fn is_terminal(self) -> bool {
        matches!(
            self,
            TaskState::Completed | TaskState::Failed | TaskState::Canceled | TaskState::Rejected
        )
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Task {
    pub id: String,
    pub context_id: String,
    pub status: TaskStatus,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub history: Vec<Message>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub artifacts: Vec<Artifact>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub metadata: HashMap<String, Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TaskStatus {
    pub state: TaskState,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub message: Option<Message>,
    pub timestamp: DateTime<Utc>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MessageRole {
    User,
    Agent,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Message {
    pub message_id: String,
    pub role: MessageRole,
    pub parts: Vec<Part>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub task_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub context_id: Option<String>,
    /// Free-form key/value map per A2A v1.0 §5.4. CAR honors two
    /// well-known keys for multi-turn correlation (Parslee-ai/car#179):
    ///
    /// - `correlationId` — pair a follow-up reply with the original
    ///   request when the peer doesn't issue a `taskId` (telemetry →
    ///   improvement-patch loops, where neither side wants the task-
    ///   lifecycle ceremony).
    /// - `parentMessageId` — `message_id` of the message this one is
    ///   replying to. Threading without task scope.
    ///
    /// Both keys are advisory: bridges (FFI observers, push
    /// dispatchers) round-trip them through `metadata` unchanged but
    /// don't enforce semantics. Peers that ignore them stay
    /// spec-conformant. Implementations that honor them MUST surface
    /// the field name verbatim — no snake_case alias.
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub metadata: HashMap<String, Value>,
}

/// A message or artifact "part". The wire discriminator is `kind`,
/// per the A2A v1.0 schema (not `type`, which would collide with
/// security-scheme tagging).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "camelCase")]
pub enum Part {
    #[serde(rename = "text")]
    Text(TextPart),
    #[serde(rename = "file")]
    File(FilePart),
    #[serde(rename = "data")]
    Data(DataPart),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TextPart {
    pub text: String,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub metadata: HashMap<String, Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FilePart {
    pub file: FileContent,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub metadata: HashMap<String, Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileContent {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub mime_type: Option<String>,
    /// Inline base64-encoded bytes. Mutually exclusive with `uri` per
    /// the spec — we do not enforce that here; consumers should
    /// validate.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub bytes: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub uri: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataPart {
    pub data: Value,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub metadata: HashMap<String, Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Artifact {
    pub artifact_id: String,
    pub name: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    pub parts: Vec<Part>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub metadata: HashMap<String, Value>,
}

// --- RPC method params / results -------------------------------------------

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SendMessageParams {
    pub message: Message,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub configuration: Option<MessageConfiguration>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MessageConfiguration {
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub accepted_output_modes: Vec<String>,
    #[serde(default)]
    pub blocking: bool,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub history_length: Option<u32>,
}

/// `message/send` returns either a Task (when the agent created one)
/// or a direct Message reply (for trivial synchronous replies).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SendMessageResult {
    Task(Task),
    Message(Message),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GetTaskParams {
    pub id: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub history_length: Option<u32>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListTasksParams {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub context_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub state: Option<TaskState>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub limit: Option<u32>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListTasksResult {
    pub tasks: Vec<Task>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PushNotificationConfig {
    pub url: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub token: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub authentication: Option<Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PushNotificationConfigParams {
    pub task_id: String,
    pub config: PushNotificationConfig,
}

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

    #[test]
    fn task_state_serializes_kebab_case() {
        assert_eq!(
            serde_json::to_string(&TaskState::InputRequired).unwrap(),
            "\"input-required\""
        );
        assert_eq!(
            serde_json::to_string(&TaskState::Submitted).unwrap(),
            "\"submitted\""
        );
        assert_eq!(
            serde_json::to_string(&TaskState::AuthRequired).unwrap(),
            "\"auth-required\""
        );
    }

    #[test]
    fn part_uses_kind_discriminator() {
        let part = Part::Text(TextPart {
            text: "hi".into(),
            metadata: HashMap::new(),
        });
        let v = serde_json::to_value(&part).unwrap();
        assert_eq!(v["kind"], "text");
        assert_eq!(v["text"], "hi");
    }

    #[test]
    fn message_round_trips_camel_case() {
        let msg = Message {
            message_id: "m-1".into(),
            role: MessageRole::User,
            parts: vec![Part::Text(TextPart {
                text: "hello".into(),
                metadata: HashMap::new(),
            })],
            task_id: Some("t-1".into()),
            context_id: None,
            metadata: HashMap::new(),
        };
        let v = serde_json::to_value(&msg).unwrap();
        assert_eq!(v["messageId"], "m-1");
        assert_eq!(v["taskId"], "t-1");
        let parsed: Message = serde_json::from_value(v).unwrap();
        assert_eq!(parsed.message_id, "m-1");
        assert_eq!(parsed.role, MessageRole::User);
    }

    #[test]
    fn agent_card_emits_camel_case() {
        let card = AgentCard {
            name: "CAR".into(),
            description: "test".into(),
            url: "https://example.com".into(),
            version: "1.0.0".into(),
            protocol_version: "1.0".into(),
            preferred_transport: Some("JSONRPC".into()),
            provider: AgentProvider {
                organization: "Parslee".into(),
                url: None,
            },
            capabilities: AgentCapabilities {
                streaming: true,
                push_notifications: false,
                state_transition_history: true,
                extended_agent_card: false,
                extensions: Vec::new(),
            },
            default_input_modes: vec!["text".into()],
            default_output_modes: vec!["text".into()],
            skills: vec![],
            documentation_url: None,
            icon_url: None,
            supported_interfaces: vec![],
            additional_interfaces: vec![AgentInterface {
                url: "wss://example.com".into(),
                protocol_binding: "JSONRPC".into(),
                transport: Some(TransportProtocol::JsonRpc),
                tenant: None,
                protocol_version: "1.0".into(),
            }],
            security_schemes: HashMap::new(),
            supports_authenticated_extended_card: false,
            security_requirements: vec![],
            signatures: vec![],
        };
        let v = serde_json::to_value(&card).unwrap();
        assert_eq!(v["defaultInputModes"][0], "text");
        assert_eq!(v["capabilities"]["pushNotifications"], false);
        assert_eq!(v["additionalInterfaces"][0]["transport"], "JSONRPC");
    }
}
