use serde::{Deserialize, Serialize};

/// ACP permission option presented to the user
///
/// When a tool requires permission, the system presents the user with a list of
/// options that define how they want to handle the request. Each option has a kind
/// that determines the scope of the permission grant or rejection.
///
/// # Examples
///
/// ```rust
/// use dirigent_protocol::{PermissionOption, PermissionOptionKind};
///
/// let allow_once = PermissionOption {
///     option_id: "allow_once_1".to_string(),
///     name: "Allow this time".to_string(),
///     kind: PermissionOptionKind::AllowOnce,
/// };
///
/// let allow_always = PermissionOption {
///     option_id: "allow_always_1".to_string(),
///     name: "Always allow for this session".to_string(),
///     kind: PermissionOptionKind::AllowAlways,
/// };
/// ```
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PermissionOption {
    /// Unique identifier for this permission option
    #[serde(rename = "optionId")]
    pub option_id: String,
    /// User-facing name/label for this option
    pub name: String,
    /// The kind of permission action this option represents
    pub kind: PermissionOptionKind,
}

/// Kind of permission option defining scope of grant/rejection
///
/// These variants define how the user's permission decision should be applied:
/// - **Once**: Applies to the current request only
/// - **Always**: Applies to all similar requests in the session/context
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum PermissionOptionKind {
    /// Grant permission for this single request
    AllowOnce,
    /// Grant permission for all similar requests
    AllowAlways,
    /// Reject this single request
    RejectOnce,
    /// Reject all similar requests
    RejectAlways,
}

/// Response from a permission request
///
/// When the system requests permission from the user, the response indicates
/// either that the user selected a specific option, or that they cancelled
/// the request entirely.
///
/// # ACP Wire Format
///
/// The response is structured with the outcome nested inside an `outcome` field:
/// ```json
/// {"outcome": {"outcome": "selected", "optionId": "allow_once_1"}}
/// ```
///
/// # Examples
///
/// ```rust
/// use dirigent_protocol::{RequestPermissionResponse, RequestPermissionOutcome};
///
/// // User selected an option
/// let selected = RequestPermissionResponse {
///     outcome: RequestPermissionOutcome::Selected {
///         option_id: "allow_once_1".to_string(),
///     },
/// };
///
/// // User cancelled the request
/// let cancelled = RequestPermissionResponse {
///     outcome: RequestPermissionOutcome::Cancelled,
/// };
/// ```
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RequestPermissionResponse {
    /// The outcome of the permission request (contains optionId if selected)
    pub outcome: RequestPermissionOutcome,
}

/// Outcome of a permission request
///
/// Uses internal tagging to produce the ACP wire format:
/// - Selected: `{"outcome": "selected", "optionId": "..."}`
/// - Cancelled: `{"outcome": "cancelled"}`
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(tag = "outcome", rename_all = "snake_case")]
pub enum RequestPermissionOutcome {
    /// User selected one of the provided options
    Selected {
        /// The ID of the selected option
        #[serde(rename = "optionId")]
        option_id: String,
    },
    /// User cancelled the permission request
    Cancelled,
}

/// Information about a tool call for permission requests
///
/// When requesting permission for a tool execution, this provides context
/// about what the tool will do, including its kind, status, and affected locations.
///
/// # Examples
///
/// ```rust
/// use dirigent_protocol::{ToolCallInfo, ToolKind, ToolCallStatus, ToolCallLocation};
///
/// let info = ToolCallInfo {
///     tool_call_id: "call_123".to_string(),
///     title: "Read configuration file".to_string(),
///     kind: Some(ToolKind::Read),
///     status: Some(ToolCallStatus::Pending),
///     locations: Some(vec![
///         ToolCallLocation {
///             path: "/etc/config.toml".to_string(),
///             line: None,
///         }
///     ]),
///     raw_input: None,
/// };
/// ```
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct ToolCallInfo {
    /// Unique identifier for this tool call
    #[serde(rename = "toolCallId")]
    pub tool_call_id: String,
    /// User-facing title describing what the tool will do
    pub title: String,
    /// The kind/category of operation this tool performs
    #[serde(skip_serializing_if = "Option::is_none")]
    pub kind: Option<ToolKind>,
    /// Current status of the tool call
    #[serde(skip_serializing_if = "Option::is_none")]
    pub status: Option<ToolCallStatus>,
    /// File/resource locations affected by this tool call
    #[serde(skip_serializing_if = "Option::is_none")]
    pub locations: Option<Vec<ToolCallLocation>>,
    /// Raw input parameters for debugging/inspection
    #[serde(rename = "rawInput", skip_serializing_if = "Option::is_none")]
    pub raw_input: Option<serde_json::Value>,
}

/// Category of tool operation
///
/// Provides semantic categorization of tool functionality to help users
/// understand the impact and risk level of granting permission.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ToolKind {
    /// Read-only operations (viewing files, searching)
    Read,
    /// Modify existing content
    Edit,
    /// Remove content
    Delete,
    /// Relocate content
    Move,
    /// Search operations
    Search,
    /// Execute commands or scripts
    Execute,
    /// Internal reasoning/planning (no external effects)
    Think,
    /// Fetch remote resources
    Fetch,
    /// Other/uncategorized operations
    Other,
}

/// Status of a tool call
///
/// Note: This duplicates `ToolCallStatus` from `tool.rs` for now.
/// In the future, we may consolidate these types.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ToolCallStatus {
    /// Tool call created but not yet started
    Pending,
    /// Tool call is currently executing
    #[serde(rename = "in_progress")]
    Running,
    /// Tool call completed successfully
    Completed,
    /// Tool call failed with an error
    #[serde(rename = "failed")]
    Failed,
}

/// Location affected by a tool call
///
/// Represents a file or resource that will be read, modified, or otherwise
/// affected by the tool execution. May optionally include a specific line number.
///
/// # Examples
///
/// ```rust
/// use dirigent_protocol::ToolCallLocation;
///
/// // File-level location
/// let file_location = ToolCallLocation {
///     path: "/src/main.rs".to_string(),
///     line: None,
/// };
///
/// // Specific line location
/// let line_location = ToolCallLocation {
///     path: "/src/lib.rs".to_string(),
///     line: Some(42),
/// };
/// ```
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct ToolCallLocation {
    /// File path or resource identifier
    pub path: String,
    /// Optional line number within the file
    #[serde(skip_serializing_if = "Option::is_none")]
    pub line: Option<i32>,
}

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

    #[test]
    fn test_permission_option_serialization() {
        let option = PermissionOption {
            option_id: "allow_once_1".to_string(),
            name: "Allow this time".to_string(),
            kind: PermissionOptionKind::AllowOnce,
        };

        let json = serde_json::to_value(&option).unwrap();
        assert_eq!(json["optionId"], "allow_once_1");
        assert_eq!(json["name"], "Allow this time");
        assert_eq!(json["kind"], "allow_once");
    }

    #[test]
    fn test_permission_option_kind_variants() {
        let kinds = vec![
            (PermissionOptionKind::AllowOnce, "allow_once"),
            (PermissionOptionKind::AllowAlways, "allow_always"),
            (PermissionOptionKind::RejectOnce, "reject_once"),
            (PermissionOptionKind::RejectAlways, "reject_always"),
        ];

        for (kind, expected) in kinds {
            let json = serde_json::to_string(&kind).unwrap();
            assert_eq!(json, format!(r#""{}""#, expected));
        }
    }

    #[test]
    fn test_request_permission_response_selected() {
        let response = RequestPermissionResponse {
            outcome: RequestPermissionOutcome::Selected {
                option_id: "allow_once_1".to_string(),
            },
        };

        let json = serde_json::to_value(&response).unwrap();
        // The outcome field should contain an object with nested outcome and optionId
        assert_eq!(json["outcome"]["outcome"], "selected");
        assert_eq!(json["outcome"]["optionId"], "allow_once_1");
    }

    #[test]
    fn test_request_permission_response_cancelled() {
        let response = RequestPermissionResponse {
            outcome: RequestPermissionOutcome::Cancelled,
        };

        let json = serde_json::to_value(&response).unwrap();
        // The outcome field should contain an object with just outcome
        assert_eq!(json["outcome"]["outcome"], "cancelled");
        // optionId should not be present for cancelled
        assert!(json["outcome"].get("optionId").is_none());
    }

    #[test]
    fn test_request_permission_response_wire_format() {
        // Test that the serialization matches ACP wire format exactly
        let response = RequestPermissionResponse {
            outcome: RequestPermissionOutcome::Selected {
                option_id: "allow".to_string(),
            },
        };

        let json = serde_json::to_value(&response).unwrap();
        // Should produce: {"outcome": {"outcome": "selected", "optionId": "allow"}}
        let expected = json!({
            "outcome": {
                "outcome": "selected",
                "optionId": "allow"
            }
        });
        assert_eq!(json, expected);
    }

    #[test]
    fn test_tool_call_info_serialization() {
        let info = ToolCallInfo {
            tool_call_id: "call_123".to_string(),
            title: "Read file".to_string(),
            kind: Some(ToolKind::Read),
            status: Some(ToolCallStatus::Pending),
            locations: Some(vec![ToolCallLocation {
                path: "/test.txt".to_string(),
                line: Some(10),
            }]),
            raw_input: Some(json!({"path": "/test.txt"})),
        };

        let json = serde_json::to_value(&info).unwrap();
        assert_eq!(json["toolCallId"], "call_123");
        assert_eq!(json["title"], "Read file");
        assert_eq!(json["kind"], "read");
        assert_eq!(json["status"], "pending");
        assert!(json["locations"].is_array());
        assert!(json["rawInput"].is_object());
    }

    #[test]
    fn test_tool_call_info_minimal() {
        let info = ToolCallInfo {
            tool_call_id: "call_456".to_string(),
            title: "Execute command".to_string(),
            kind: None,
            status: None,
            locations: None,
            raw_input: None,
        };

        let json = serde_json::to_value(&info).unwrap();
        assert_eq!(json["toolCallId"], "call_456");
        assert_eq!(json["title"], "Execute command");
        // Optional fields should be omitted
        assert!(json.get("kind").is_none());
        assert!(json.get("status").is_none());
        assert!(json.get("locations").is_none());
        assert!(json.get("rawInput").is_none());
    }

    #[test]
    fn test_tool_kind_variants() {
        let kinds = vec![
            (ToolKind::Read, "read"),
            (ToolKind::Edit, "edit"),
            (ToolKind::Delete, "delete"),
            (ToolKind::Move, "move"),
            (ToolKind::Search, "search"),
            (ToolKind::Execute, "execute"),
            (ToolKind::Think, "think"),
            (ToolKind::Fetch, "fetch"),
            (ToolKind::Other, "other"),
        ];

        for (kind, expected) in kinds {
            let json = serde_json::to_string(&kind).unwrap();
            assert_eq!(json, format!(r#""{}""#, expected));
        }
    }

    #[test]
    fn test_tool_call_status_variants() {
        let statuses = vec![
            (ToolCallStatus::Pending, "pending"),
            (ToolCallStatus::Running, "in_progress"),
            (ToolCallStatus::Completed, "completed"),
            (ToolCallStatus::Failed, "failed"),
        ];

        for (status, expected) in statuses {
            let json = serde_json::to_string(&status).unwrap();
            assert_eq!(json, format!(r#""{}""#, expected));
        }
    }

    #[test]
    fn test_tool_call_location_with_line() {
        let location = ToolCallLocation {
            path: "/src/main.rs".to_string(),
            line: Some(42),
        };

        let json = serde_json::to_value(&location).unwrap();
        assert_eq!(json["path"], "/src/main.rs");
        assert_eq!(json["line"], 42);
    }

    #[test]
    fn test_tool_call_location_without_line() {
        let location = ToolCallLocation {
            path: "/config.toml".to_string(),
            line: None,
        };

        let json = serde_json::to_value(&location).unwrap();
        assert_eq!(json["path"], "/config.toml");
        assert!(json.get("line").is_none());
    }

    #[test]
    fn test_roundtrip_permission_option() {
        let original = PermissionOption {
            option_id: "test_id".to_string(),
            name: "Test Option".to_string(),
            kind: PermissionOptionKind::AllowAlways,
        };

        let json = serde_json::to_string(&original).unwrap();
        let deserialized: PermissionOption = serde_json::from_str(&json).unwrap();

        assert_eq!(original, deserialized);
    }

    #[test]
    fn test_roundtrip_tool_call_info() {
        let original = ToolCallInfo {
            tool_call_id: "call_789".to_string(),
            title: "Complex operation".to_string(),
            kind: Some(ToolKind::Edit),
            status: Some(ToolCallStatus::Running),
            locations: Some(vec![
                ToolCallLocation {
                    path: "/file1.rs".to_string(),
                    line: Some(10),
                },
                ToolCallLocation {
                    path: "/file2.rs".to_string(),
                    line: None,
                },
            ]),
            raw_input: Some(json!({"key": "value"})),
        };

        let json = serde_json::to_string(&original).unwrap();
        let deserialized: ToolCallInfo = serde_json::from_str(&json).unwrap();

        assert_eq!(original, deserialized);
    }
}
