use std::ops::DerefMut;

use serde_derive::Serialize;
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;

use crate::errors::DatabaseError;

#[derive(Debug, Serialize, Clone)]
pub struct DbTag {
	pub id: i64,
	pub tag: String,
}

impl DbTag {
	pub async fn create(
		conn: &mut PoolConnection<Sqlite>,
		new_tag: &str,
	) -> Result<DbTag, DatabaseError> {
		if new_tag.is_empty() {
			return Err(DatabaseError::EmptyDataInsert);
		}

		sqlx::query_as!(
			DbTag,
			"INSERT INTO tags (tag) VALUES (?) RETURNING *",
			new_tag
		)
		.fetch_optional(conn.deref_mut())
		.await?
		.ok_or(DatabaseError::InsertGetError)
	}

	pub async fn get_all(conn: &mut PoolConnection<Sqlite>) -> Result<Vec<DbTag>, DatabaseError> {
		sqlx::query_as!(DbTag, "SELECT * FROM tags")
			.fetch_all(conn.deref_mut())
			.await
			.map_err(DatabaseError::from)
	}

	pub async fn get(
		conn: &mut PoolConnection<Sqlite>,
		id: i64,
	) -> Result<Option<DbTag>, DatabaseError> {
		sqlx::query_as!(DbTag, "SELECT * FROM tags WHERE id = ?", id)
			.fetch_optional(conn.deref_mut())
			.await
			.map_err(DatabaseError::from)
	}

	pub async fn get_by_tag_or_create(
		conn: &mut PoolConnection<Sqlite>,
		target_tag: &str,
	) -> Result<DbTag, DatabaseError> {
		match DbTag::get_by_tag(conn, target_tag).await? {
			Some(tag) => Ok(tag),
			None => DbTag::create(conn, target_tag).await,
		}
	}

	pub async fn get_by_tag(
		conn: &mut PoolConnection<Sqlite>,
		target_tag: &str,
	) -> Result<Option<DbTag>, DatabaseError> {
		sqlx::query_as!(DbTag, "SELECT * FROM tags WHERE tag = ?", target_tag)
			.fetch_optional(conn.deref_mut())
			.await
			.map_err(DatabaseError::from)
	}

	pub async fn delete_by_tag(
		conn: &mut PoolConnection<Sqlite>,
		filter_tag: &str,
	) -> Result<(), DatabaseError> {
		if sqlx::query_scalar!("SELECT 1 FROM tags WHERE tag = ?", filter_tag)
			.fetch_optional(conn.deref_mut())
			.await?
			.is_none()
		{
			return Err(DatabaseError::NotFound);
		}

		sqlx::query!("DELETE FROM tags WHERE tag = ?", filter_tag)
			.execute(conn.deref_mut())
			.await
			.map(|res| match res.rows_affected() {
				0 => Err(DatabaseError::DeleteError),
				_ => Ok(()),
			})?
	}
}