pub mod channel_data;
pub mod command_macro;
pub mod guild_data;
pub mod reminder;
pub mod timer;
pub mod user_data;

use chrono_tz::Tz;
use poise::serenity_prelude::{async_trait, model::id::UserId};

use crate::{
    models::{channel_data::ChannelData, guild_data::GuildData, user_data::UserData},
    CommandMacro, Context, Data, Error, GuildId,
};

#[async_trait]
pub trait CtxData {
    async fn user_data<U: Into<UserId> + Send>(&self, user_id: U) -> Result<UserData, Error>;

    async fn author_data(&self) -> Result<UserData, Error>;

    async fn guild_data(&self) -> Option<Result<GuildData, Error>>;

    async fn timezone(&self) -> Tz;

    async fn channel_data(&self) -> Result<ChannelData, Error>;

    async fn command_macros(&self) -> Result<Vec<CommandMacro>, Error>;
}

#[async_trait]
impl CtxData for Context<'_> {
    async fn user_data<U: Into<UserId> + Send>(&self, user_id: U) -> Result<UserData, Error> {
        UserData::from_user(user_id, &self.serenity_context(), &self.data().database).await
    }

    async fn author_data(&self) -> Result<UserData, Error> {
        UserData::from_user(&self.author().id, &self.serenity_context(), &self.data().database)
            .await
    }

    async fn guild_data(&self) -> Option<Result<GuildData, Error>> {
        if let Some(guild_id) = self.guild_id() {
            Some(GuildData::from_guild(guild_id, &self.data().database).await)
        } else {
            None
        }
    }

    async fn timezone(&self) -> Tz {
        UserData::timezone_of(self.author().id, &self.data().database).await
    }

    async fn channel_data(&self) -> Result<ChannelData, Box<dyn std::error::Error + Sync + Send>> {
        // If we're in a thread, get the parent channel.
        let channel = self.channel_id().to_channel(&self).await?;
        ChannelData::from_channel(&channel, &self.data().database).await
    }

    async fn command_macros(&self) -> Result<Vec<CommandMacro>, Error> {
        self.data().command_macros(self.guild_id().unwrap()).await
    }
}

impl Data {
    pub(crate) async fn command_macros(
        &self,
        guild_id: GuildId,
    ) -> Result<Vec<CommandMacro>, Error> {
        let rows = sqlx::query!(
            "SELECT name, description, commands FROM command_macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
            guild_id.get()
        )
        .fetch_all(&self.database)
        .await?.iter().map(|row| CommandMacro {
            guild_id,
            name: row.name.clone(),
            description: row.description.clone(),
            commands: serde_json::from_str(&row.commands.to_string()).unwrap(),
        }).collect();

        Ok(rows)
    }
}
