use serde::{Deserialize, Serialize};

/// A single option in a permission prompt.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PermissionOption {
    /// Machine-readable identifier for this option (e.g. `"allow_once"`).
    pub option_id: String,
    /// Human-readable label shown to the user.
    ///
    /// Accepts `"name"` as an alias on deserialization for compatibility with
    /// agents that use `name` instead of `label` (e.g. OpenCode).
    #[serde(alias = "name")]
    pub label: String,
}

/// Permission request from agent, forwarded to channel for user decision.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PermissionRequest {
    /// Unique identifier for correlating the response.
    pub request_id: String,
    /// Human-readable description of what the agent is requesting.
    pub description: String,
    /// Available choices the user can select from.
    pub options: Vec<PermissionOption>,
}

/// User's response to a permission request.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PermissionResponse {
    /// Identifier matching the original [`PermissionRequest`].
    pub request_id: String,
    /// The `option_id` the user selected.
    pub option_id: String,
}

/// Anyclaw → Channel: show permission prompt to user.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ChannelRequestPermission {
    /// Unique identifier for correlating the response.
    pub request_id: String,
    /// Session the permission request belongs to.
    pub session_id: String,
    /// Human-readable description of what is being requested.
    ///
    /// Deprecated: channels should format their own description from the
    /// structured fields (`tool_kind`, `target`, `title`). This field is
    /// kept empty for backward compatibility with raw JSON-RPC channels.
    #[serde(default)]
    pub description: String,
    /// Available choices the user can select from.
    pub options: Vec<PermissionOption>,
    /// Verb describing the tool action (e.g. "Read", "Edit", "Execute").
    /// Derived from the ACP `ToolKind` enum.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tool_kind: Option<String>,
    /// The primary target of the action (file path, URL, command, etc.).
    /// Extracted from `locations[0].path` or `rawInput` keys.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub target: Option<String>,
    /// The original tool name reported by the agent (e.g. "external_directory").
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
}

/// Anyclaw → Channel: cancel a previously-sent permission prompt.
///
/// The channel should update or remove the approval UI (e.g. remove inline
/// keyboard buttons in Telegram) and optionally display the cancellation reason.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ChannelCancelPermission {
    /// The `request_id` of the original permission request being cancelled.
    pub request_id: String,
    /// Session the permission request belongs to.
    pub session_id: String,
    /// Human-readable reason for cancellation (e.g. "agent restarted").
    pub reason: String,
}

impl ChannelRequestPermission {
    /// Build a human-readable description from the structured fields.
    ///
    /// Channels that want custom formatting can use `tool_kind`, `target`,
    /// and `title` directly. Channels that just want a reasonable string
    /// can call this method.
    pub fn default_description(&self) -> String {
        match (self.tool_kind.as_deref(), self.target.as_deref()) {
            (Some(verb), Some(t)) => format!("{verb}: {t}"),
            (Some(verb), None) => self
                .title
                .as_deref()
                .filter(|t| !t.is_empty())
                .map_or_else(|| verb.to_string(), |t| format!("{verb}: {t}")),
            (None, Some(t)) => self.title.as_deref().filter(|t| !t.is_empty()).map_or_else(
                || format!("Permission requested: {t}"),
                |ti| format!("{ti}: {t}"),
            ),
            (None, None) => self
                .title
                .as_deref()
                .filter(|t| !t.is_empty())
                .unwrap_or("Permission requested")
                .to_string(),
        }
    }
}

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

    #[rstest]
    #[test]
    fn when_serializing_permission_option_then_uses_camel_case() {
        let opt = PermissionOption {
            option_id: "allow_once".into(),
            label: "Allow once".into(),
        };
        let json = serde_json::to_value(&opt).unwrap();
        assert_eq!(json["optionId"], "allow_once");
        assert_eq!(json["label"], "Allow once");
        assert!(json.get("option_id").is_none());
        let deser: PermissionOption = serde_json::from_value(json).unwrap();
        assert_eq!(deser, opt);
    }

    #[rstest]
    #[test]
    fn when_serializing_permission_request_then_uses_camel_case() {
        let req = PermissionRequest {
            request_id: "perm-1".into(),
            description: "Allow file write?".into(),
            options: vec![
                PermissionOption {
                    option_id: "allow".into(),
                    label: "Allow".into(),
                },
                PermissionOption {
                    option_id: "deny".into(),
                    label: "Deny".into(),
                },
            ],
        };
        let json = serde_json::to_value(&req).unwrap();
        assert_eq!(json["requestId"], "perm-1");
        assert_eq!(json["description"], "Allow file write?");
        assert_eq!(json["options"].as_array().unwrap().len(), 2);
        assert!(json.get("request_id").is_none());
        let deser: PermissionRequest = serde_json::from_value(json).unwrap();
        assert_eq!(deser, req);
    }

    #[rstest]
    #[test]
    fn when_serializing_permission_response_then_uses_camel_case() {
        let resp = PermissionResponse {
            request_id: "perm-1".into(),
            option_id: "allow_once".into(),
        };
        let json = serde_json::to_value(&resp).unwrap();
        assert_eq!(json["requestId"], "perm-1");
        assert_eq!(json["optionId"], "allow_once");
        assert!(json.get("request_id").is_none());
        let deser: PermissionResponse = serde_json::from_value(json).unwrap();
        assert_eq!(deser, resp);
    }

    #[rstest]
    #[test]
    fn when_deserializing_permission_option_with_name_alias_then_maps_to_label() {
        let json = serde_json::json!({"optionId": "once", "name": "Allow once"});
        let opt: PermissionOption = serde_json::from_value(json).unwrap();
        assert_eq!(opt.label, "Allow once");
        assert_eq!(opt.option_id, "once");
    }

    #[rstest]
    #[test]
    fn when_serializing_channel_request_permission_then_uses_camel_case() {
        let req = ChannelRequestPermission {
            request_id: "req-1".into(),
            session_id: "sess-1".into(),
            description: String::new(),
            options: vec![PermissionOption {
                option_id: "allow".into(),
                label: "Allow".into(),
            }],
            tool_kind: Some("Read".into()),
            target: Some("/tmp/foo".into()),
            title: Some("external_directory".into()),
        };
        let json = serde_json::to_value(&req).unwrap();
        assert_eq!(json["requestId"], "req-1");
        assert_eq!(json["sessionId"], "sess-1");
        assert_eq!(json["toolKind"], "Read");
        assert_eq!(json["target"], "/tmp/foo");
        assert_eq!(json["title"], "external_directory");
        assert!(json.get("request_id").is_none());
        assert!(json.get("session_id").is_none());
        let deser: ChannelRequestPermission = serde_json::from_value(json).unwrap();
        assert_eq!(deser, req);
    }

    // ── Round-trip serde tests (04-02 Task 1) ──────────────────────────

    #[rstest]
    fn when_permission_option_round_trips_then_identical() {
        let original = PermissionOption {
            option_id: "allow_once".into(),
            label: "Allow once".into(),
        };
        let json = serde_json::to_value(&original).unwrap();
        let restored: PermissionOption = serde_json::from_value(json).unwrap();
        assert_eq!(original, restored);
    }

    #[rstest]
    fn when_permission_request_round_trips_then_identical() {
        let original = PermissionRequest {
            request_id: "perm-1".into(),
            description: "Allow file write to /tmp?".into(),
            options: vec![
                PermissionOption {
                    option_id: "allow".into(),
                    label: "Allow".into(),
                },
                PermissionOption {
                    option_id: "deny".into(),
                    label: "Deny".into(),
                },
            ],
        };
        let json = serde_json::to_value(&original).unwrap();
        let restored: PermissionRequest = serde_json::from_value(json).unwrap();
        assert_eq!(original, restored);
    }

    #[rstest]
    fn when_permission_request_empty_options_round_trips_then_identical() {
        let original = PermissionRequest {
            request_id: "perm-2".into(),
            description: "No options".into(),
            options: vec![],
        };
        let json = serde_json::to_value(&original).unwrap();
        let restored: PermissionRequest = serde_json::from_value(json).unwrap();
        assert_eq!(original, restored);
    }

    #[rstest]
    fn when_permission_response_round_trips_then_identical() {
        let original = PermissionResponse {
            request_id: "perm-1".into(),
            option_id: "allow_once".into(),
        };
        let json = serde_json::to_value(&original).unwrap();
        let restored: PermissionResponse = serde_json::from_value(json).unwrap();
        assert_eq!(original, restored);
    }

    #[rstest]
    fn when_channel_request_permission_round_trips_then_identical() {
        let original = ChannelRequestPermission {
            request_id: "req-1".into(),
            session_id: "sess-1".into(),
            description: String::new(),
            options: vec![PermissionOption {
                option_id: "allow".into(),
                label: "Allow".into(),
            }],
            tool_kind: Some("Read".into()),
            target: Some("/tmp/foo".into()),
            title: Some("external_directory".into()),
        };
        let json = serde_json::to_value(&original).unwrap();
        let restored: ChannelRequestPermission = serde_json::from_value(json).unwrap();
        assert_eq!(original, restored);
    }

    #[rstest]
    #[case::verb_and_target(Some("Read"), Some("/tmp/foo"), None, "Read: /tmp/foo")]
    #[case::verb_only_with_title(Some("Edit"), None, Some("config.yaml"), "Edit: config.yaml")]
    #[case::verb_only_no_title(Some("Execute"), None, None, "Execute")]
    #[case::target_only_with_title(
        None,
        Some("/tmp/foo"),
        Some("external_directory"),
        "external_directory: /tmp/foo"
    )]
    #[case::target_only_no_title(None, Some("/tmp/foo"), None, "Permission requested: /tmp/foo")]
    #[case::nothing_with_title(None, None, Some("external_directory"), "external_directory")]
    #[case::nothing_at_all(None, None, None, "Permission requested")]
    fn when_default_description_called_then_formats_from_structured_fields(
        #[case] tool_kind: Option<&str>,
        #[case] target: Option<&str>,
        #[case] title: Option<&str>,
        #[case] expected: &str,
    ) {
        let req = ChannelRequestPermission {
            request_id: "req-1".into(),
            session_id: "sess-1".into(),
            description: String::new(),
            options: vec![],
            tool_kind: tool_kind.map(Into::into),
            target: target.map(Into::into),
            title: title.map(Into::into),
        };
        assert_eq!(req.default_description(), expected);
    }

    #[rstest]
    fn when_structured_fields_missing_then_deserializes_with_defaults() {
        // Simulates a raw JSON-RPC channel that doesn't send the new fields.
        let json = serde_json::json!({
            "requestId": "req-1",
            "sessionId": "sess-1",
            "description": "legacy description",
            "options": []
        });
        let req: ChannelRequestPermission = serde_json::from_value(json).unwrap();
        assert_eq!(req.tool_kind, None);
        assert_eq!(req.target, None);
        assert_eq!(req.title, None);
        assert_eq!(req.description, "legacy description");
    }

    #[rstest]
    fn when_channel_cancel_permission_round_trips_then_identical() {
        let original = ChannelCancelPermission {
            request_id: "req-1".into(),
            session_id: "sess-1".into(),
            reason: "agent restarted".into(),
        };
        let json = serde_json::to_value(&original).unwrap();
        assert_eq!(json["requestId"], "req-1");
        assert_eq!(json["sessionId"], "sess-1");
        assert_eq!(json["reason"], "agent restarted");
        assert!(json.get("request_id").is_none());
        let restored: ChannelCancelPermission = serde_json::from_value(json).unwrap();
        assert_eq!(original, restored);
    }
}
