use crate::handlers::ReactionHandler;
use crate::models::ServerSetting;
use crate::schema;
use crate::schema::{server_settings, starboard_mappings};

use anyhow::Context as AnyhowContext;
use bigdecimal::{BigDecimal, FromPrimitive, ToPrimitive};
use diesel::{BoolExpressionMethods, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
use serenity::async_trait;
use serenity::model::prelude::{
	ChannelId, Embed, EmojiId, GuildChannel, GuildId, Message, Reaction, ReactionType,
};
use serenity::prelude::*;
use std::sync::Arc;
use tokio::sync::Mutex;

pub struct StarboardHandler {
	conn: Arc<Mutex<PgConnection>>,
}

impl StarboardHandler {
	pub fn new(conn: Arc<Mutex<PgConnection>>) -> Self {
		Self { conn }
	}

	async fn handle_reaction(&self, ctx: &Context, reaction: &Reaction) -> anyhow::Result<()> {
		let guild = match reaction.channel_id.to_channel(ctx).await?.guild() {
			Some(x) => x,
			None => return Ok(()),
		};

		// get corresponding guild settings
		let gs: Vec<ServerSetting> = server_settings::dsl::server_settings
			.filter(
				schema::server_settings::columns::guild_id
					.eq(BigDecimal::from_u64(guild.guild_id.0).unwrap()),
			)
			.limit(1)
			.get_results(&*self.conn.lock().await)?;
		let gs = match gs.first() {
			Some(x) => x,
			None => return Ok(()),
		};
		let emoji = EmojiId(
			gs.starboard_emoji_id
				.to_u64()
				.context("Could not convert emoji id to u64")?,
		);

		if Self::emoji_match(&reaction.emoji, emoji) {
			let msg = reaction.message(ctx).await?;
			for mr in &msg.reactions {
				if Self::emoji_match(&mr.reaction_type, emoji) {
					self.handle_matching_reaction(ctx, gs, mr.count, &msg, &guild.guild_id)
						.await?;
					break;
				}
			}
		}

		Ok(())
	}

	async fn handle_matching_reaction(
		&self,
		ctx: &Context,
		gs: &ServerSetting,
		count: u64,
		msg: &Message,
		guild: &GuildId,
	) -> anyhow::Result<()> {
		if count >= gs.starboard_threshold as u64 {
			let original_id = BigDecimal::from(msg.id.0);
			diesel::insert_into(starboard_mappings::dsl::starboard_mappings)
				.values(starboard_mappings::columns::original_id.eq(&original_id))
				.returning(starboard_mappings::columns::repost_id)
				.on_conflict_do_nothing()
				.execute(&*self.conn.lock().await)?;

			let repost_id = starboard_mappings::dsl::starboard_mappings
				.filter(starboard_mappings::columns::original_id.eq(&original_id))
				.select(starboard_mappings::columns::repost_id)
				.limit(1)
				.get_results(&*self.conn.lock().await)?;

			let repost_id: &Option<BigDecimal> =
				repost_id.get(0).context("Insert of mapping failed")?;

			if repost_id.is_none() {
				// post to repost channel
				let name = msg
					.author
					.nick_in(&ctx, guild)
					.await
					.unwrap_or_else(|| msg.author.tag());

				let image = msg
					.attachments
					.iter()
					.filter(|a| a.width.is_some())
					.map(|a| &a.url)
					.next();

				let repost = ChannelId(
					gs.starboard_channel
						.to_u64()
						.context("Could not convert starboard channel to a u64")?,
				)
				.send_message(ctx, |m| {
					m.embed(|e| {
						let mut e = e
							.description(format!(
								"[Jump to source]({})\n{}",
								msg.link(),
								msg.content
							))
							.author(|a| a.name(&name).icon_url(msg.author.face()))
							.timestamp(&msg.timestamp);

						if let Some(url) = image {
							e = e.image(url);
						}

						e
					})
				})
				.await?;

				// update the DB
				let repost_id = BigDecimal::from_u64(repost.id.0);
				diesel::update(
					starboard_mappings::dsl::starboard_mappings
						.filter(starboard_mappings::columns::original_id.eq(original_id)),
				)
				.set(starboard_mappings::columns::repost_id.eq(repost_id))
				.execute(&*self.conn.lock().await)?;
			}
		}

		Ok(())
	}

	fn emoji_match(rt: &ReactionType, em: EmojiId) -> bool {
		matches!(rt, ReactionType::Custom { id, .. } if *id == em)
	}
}

#[async_trait]
impl ReactionHandler for StarboardHandler {
	async fn reaction(&self, ctx: &Context, reaction: &Reaction) {
		if let Err(e) = self.handle_reaction(ctx, reaction).await {
			eprintln!("Error in starboard: {:?}", e);
		}
	}
}