use crate::frames::up_message::robot_message::{RobotGroupMessage, RobotMessage, RobotPrivateMessage};
use crate::frames::{SendMessageCallback, SendMessageCallbackData};
use crate::{ROBOT_SEND_GROUP_MESSAGE, ROBOT_SEND_PRIVATE_MESSAGE};
use anyhow::anyhow;
use serde_json::json;
use log::info;

impl super::DingTalkStream {
    pub async fn send_message<M: Into<RobotMessage>>(&self, message: M) -> crate::Result<()> {
        let message = message.into();
        let http_client = self.http_client.clone();
        let credential = self.credential.clone();
        let access_token = self.get_access_token().await?;
        match message {
            RobotMessage::Private {
                message,
                send_result_cb,
            } => {
                let _ = Self::send_private_message(
                    &http_client,
                    &access_token,
                    &credential.client_id,
                    &message,
                    send_result_cb.as_ref(),
                )
                .await?;
            }
            RobotMessage::Group {
                message,
                send_result_cb,
            } => {
                let _ = Self::send_group_message(
                    &http_client,
                    &access_token,
                    &credential.client_id,
                    &message,
                    send_result_cb.as_ref(),
                )
                .await?;
            }
        };
        Ok(())
    }
}

impl super::DingTalkStream {
    async fn send_private_message(
        http_client: &reqwest::Client,
        access_token: &str,
        client_id: &str,
        RobotPrivateMessage { user_ids, content }: &RobotPrivateMessage,
        send_result_cb: Option<&SendMessageCallback>,
    ) -> crate::Result<()> {
        let (msg_key, msg_param) = content.to_up_msg()?;
        let msg_md5 = format!("{:x}", md5::compute(&msg_param));
        info!("Sending private robot-message[{}]: {}", msg_md5, msg_param);
        let response = http_client
            .post(ROBOT_SEND_PRIVATE_MESSAGE)
            .header("x-acs-dingtalk-access-token", access_token)
            .header("Content-Type", "application/json")
            .json(&json!({
                "robotCode": client_id,
                "userIds": user_ids,
                "msgParam": msg_param,
                "msgKey": msg_key
            }))
            .send()
            .await;
        if let Some(cb) = send_result_cb {
            Self::exec_send_cb(response, cb).await;
        }
        info!("Send private robot-message[{}] ok", msg_md5);
        Ok(())
    }

    async fn send_group_message(
        http_client: &reqwest::Client,
        access_token: &str,
        client_id: &str,
        RobotGroupMessage { group_id, content }: &RobotGroupMessage,
        send_result_cb: Option<&SendMessageCallback>,
    ) -> crate::Result<()> {
        let (msg_key, msg_param) = content.to_up_msg()?;
        let msg_md5 = format!("{:x}", md5::compute(&msg_param));
        info!("Sending group robot-message[{}]: {}", msg_md5, msg_param);
        let response = http_client
            .post(ROBOT_SEND_GROUP_MESSAGE)
            .header("x-acs-dingtalk-access-token", access_token)
            .header("Content-Type", "application/json")
            .json(&json!({
                "robotCode": client_id,
                "openConversationId": group_id,
                "msgParam": msg_param,
                "msgKey": msg_key
            }))
            .send()
            .await;
        if let Some(cb) = send_result_cb {
            Self::exec_send_cb(response, cb).await;
        }
        info!("Send group robot-message[{}] ok", msg_md5);
        Ok(())
    }

    async fn exec_send_cb(
        response: Result<reqwest::Response, reqwest::Error>,
        cb: &SendMessageCallback,
    ) {
        match response {
            Ok(response) => {
                let code = response.status();
                match response.text().await {
                    Ok(text) => cb(Ok(SendMessageCallbackData {
                        http_status: code.as_u16(),
                        text,
                    })),
                    Err(err) => cb(Err(anyhow!("{err}"))),
                }
            }
            Err(err) => cb(Err(anyhow!("{err}"))),
        }
    }
}
