use poise::{
    serenity_prelude::{GuildId, UserId},
    CreateReply,
};
use serde_json::Value;
use serenity::all::Http;
use serenity::http::CacheHttp;
use tokio::sync::Mutex;

use crate::{Data, Error};

pub(crate) struct TestData {
    pub(crate) replies: Vec<CreateReply>,
}

#[derive(Copy, Clone)]
pub(crate) struct TestContext<'a> {
    pub(crate) data: &'a Data,
    pub(crate) cache: &'a MockCache,
    pub(crate) test_data: &'a Mutex<TestData>,
    pub(crate) shard_id: usize,
}

impl CacheHttp for TestContext<'_> {
    fn http(&self) -> &Http {
        todo!()
    }
}

pub(crate) struct MockUser {
    pub(crate) id: UserId,
}

pub(crate) struct MockCache {}

impl<'a> TestContext<'a> {
    pub async fn say(&self, message: impl Into<String>) -> Result<(), Error> {
        self.test_data.lock().await.replies.push(CreateReply::default().content(message));

        Ok(())
    }

    pub async fn send(&self, reply: CreateReply) -> Result<(), Error> {
        self.test_data.lock().await.replies.push(reply.clone());

        Ok(())
    }

    pub fn guild_id(&self) -> Option<GuildId> {
        Some(GuildId::new(1))
    }

    pub async fn defer_ephemeral(&self) -> Result<(), Error> {
        Ok(())
    }

    pub fn author(&self) -> MockUser {
        MockUser { id: UserId::new(1) }
    }

    pub fn data(&self) -> &Data {
        return &self.data;
    }

    pub fn serenity_context(&self) -> &Self {
        return &self;
    }

    pub async fn sent_content(&self) -> Vec<String> {
        let data = self.test_data.lock().await;

        data.replies
            .iter()
            .map(|r| {
                let reply = r.clone();
                let content = reply.content.unwrap_or(String::new());
                let embed_content = reply
                    .embeds
                    .iter()
                    .map(|e| {
                        let map = serde_json::to_value(e).unwrap();
                        let description =
                            map.get("description").cloned().unwrap_or(Value::String(String::new()));
                        return format!("{}", description.as_str().unwrap());
                    })
                    .collect::<Vec<String>>()
                    .join("\n");

                return if content.is_empty() {
                    embed_content
                } else {
                    format!("{}\n{}", content, embed_content)
                };
            })
            .collect::<Vec<String>>()
    }
}

impl MockCache {
    pub fn shard_count(&self) -> usize {
        return 1;
    }
}
