//! Send reaction action for the BlueBubbles plugin.

use crate::service::BlueBubblesService;
use crate::types::BlueBubblesSendResult;
use async_trait::async_trait;
use elizaos::types::HandlerOptions;
use elizaos::{ActionDefinition, ActionHandler, ActionResult, Memory, State};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tracing::debug;

/// Parameters for the send reaction action.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SendReactionParams {
    /// The chat GUID.
    pub chat_guid: String,
    /// The message GUID to react to.
    pub message_guid: String,
    /// The emoji reaction.
    pub emoji: String,
    /// Whether to remove the reaction.
    #[serde(default)]
    pub remove: bool,
}

/// Execute the send reaction action.
pub async fn execute_send_reaction(
    service: &BlueBubblesService,
    params: SendReactionParams,
) -> BlueBubblesSendResult {
    if !service.is_connected() {
        return BlueBubblesSendResult::failure("BlueBubbles service is not connected");
    }

    let result = service
        .send_reaction(
            &params.chat_guid,
            &params.message_guid,
            &params.emoji,
            params.remove,
        )
        .await;

    if result.success {
        let action = if params.remove { "Removed" } else { "Added" };
        debug!("{} reaction {} on {}", action, params.emoji, params.message_guid);
    }

    result
}

/// Action definition for send reaction.
pub const SEND_REACTION_ACTION: &str = "BLUEBUBBLES_SEND_REACTION";

/// Action similes.
pub const SEND_REACTION_SIMILES: &[&str] = &[
    "BLUEBUBBLES_REACT",
    "BB_REACTION",
    "IMESSAGE_REACT",
];

/// Action description.
pub const SEND_REACTION_DESCRIPTION: &str = "Add or remove a reaction on a message via BlueBubbles";

/// Template for extracting parameters via LLM.
pub const SEND_REACTION_TEMPLATE: &str = r#"# Task: Extract BlueBubbles reaction parameters

Based on the conversation, determine what reaction to add or remove.

Recent conversation:
{{recentMessages}}

Extract the following:
1. emoji: The emoji reaction to add (heart, thumbsup, thumbsdown, haha, exclamation, question, or any emoji)
2. messageId: The message ID to react to (or "last" for the last message)
3. remove: true to remove the reaction, false to add it

Respond with a JSON object:
```json
{
  "emoji": "❤️",
  "messageId": "last",
  "remove": false
}
```
"#;

/// Action struct for send reaction, implementing the ActionHandler trait.
pub struct SendReactionAction {
    service: Arc<BlueBubblesService>,
}

impl SendReactionAction {
    /// Creates a new send reaction action.
    pub fn new(service: Arc<BlueBubblesService>) -> Self {
        Self { service }
    }
}

#[async_trait]
impl ActionHandler for SendReactionAction {
    fn definition(&self) -> ActionDefinition {
        ActionDefinition {
            name: SEND_REACTION_ACTION.to_string(),
            description: SEND_REACTION_DESCRIPTION.to_string(),
            similes: Some(SEND_REACTION_SIMILES.iter().map(|s| s.to_string()).collect()),
            examples: None,
            priority: None,
            tags: None,
            parameters: None,
        }
    }

    async fn validate(&self, _message: &Memory, _state: Option<&State>) -> bool {
        self.service.client().is_some()
    }

    async fn handle(
        &self,
        _message: &Memory,
        state: Option<&State>,
        _options: Option<&HandlerOptions>,
    ) -> std::result::Result<Option<ActionResult>, anyhow::Error> {
        let chat_guid = state
            .and_then(|s| s.get_value("chatGuid"))
            .and_then(|v| v.as_str().map(String::from))
            .ok_or_else(|| anyhow::anyhow!("Could not determine chat"))?;

        let message_guid = state
            .and_then(|s| s.get_value("lastMessageGuid"))
            .and_then(|v| v.as_str().map(String::from))
            .ok_or_else(|| anyhow::anyhow!("Could not find message to react to"))?;

        // Default emoji
        let emoji = "❤️".to_string();
        let remove = false;

        let params = SendReactionParams {
            chat_guid: chat_guid.clone(),
            message_guid: message_guid.clone(),
            emoji: emoji.clone(),
            remove,
        };

        let result = execute_send_reaction(&*self.service, params).await;

        if !result.success {
            return Err(anyhow::anyhow!(
                result
                    .error
                    .unwrap_or_else(|| "Failed to send reaction".to_string())
            ));
        }

        Ok(Some(
            ActionResult::success(if remove {
                "Reaction removed.".to_string()
            } else {
                format!("Reacted with {}.", emoji)
            })
            .with_data("emoji", serde_json::json!(emoji))
            .with_data("messageGuid", serde_json::json!(message_guid))
            .with_data("removed", serde_json::json!(remove)),
        ))
    }
}
