use std::ops::DerefMut;

use serde_derive::{Deserialize, Serialize};
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;

use crate::db::{DbController, DbMacroAction, DbRelay, DbSchedule};
use crate::errors::DatabaseError;
use crate::types::{EmgauwaUid, RequestMacroAction};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DbMacro {
	#[serde(skip)]
	pub id: i64,
	#[serde(rename = "id")]
	pub uid: EmgauwaUid,
	pub name: String,
}

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

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

	pub async fn get_by_uid(
		conn: &mut PoolConnection<Sqlite>,
		filter_uid: &EmgauwaUid,
	) -> Result<Option<DbMacro>, DatabaseError> {
		sqlx::query_as!(DbMacro, "SELECT * FROM macros WHERE uid = ?", filter_uid)
			.fetch_optional(conn.deref_mut())
			.await
			.map_err(DatabaseError::from)
	}

	pub async fn create(
		conn: &mut PoolConnection<Sqlite>,
		new_uid: EmgauwaUid,
		new_name: &str,
	) -> Result<DbMacro, DatabaseError> {
		sqlx::query_as!(
			DbMacro,
			"INSERT INTO macros (uid, name) VALUES (?, ?) RETURNING *",
			new_uid,
			new_name
		)
		.fetch_optional(conn.deref_mut())
		.await?
		.ok_or(DatabaseError::InsertGetError)
	}

	pub async fn delete(&self, conn: &mut PoolConnection<Sqlite>) -> Result<(), DatabaseError> {
		sqlx::query!("DELETE FROM macros WHERE id = ?", self.id)
			.execute(conn.deref_mut())
			.await
			.map(|res| match res.rows_affected() {
				0 => Err(DatabaseError::DeleteError),
				_ => Ok(()),
			})?
	}

	pub async fn delete_by_uid(
		conn: &mut PoolConnection<Sqlite>,
		filter_uid: EmgauwaUid,
	) -> Result<(), DatabaseError> {
		if sqlx::query_scalar!("SELECT 1 FROM macros WHERE uid = ?", filter_uid)
			.fetch_optional(conn.deref_mut())
			.await?
			.is_none()
		{
			return Err(DatabaseError::NotFound);
		}

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

	pub async fn update(
		&self,
		conn: &mut PoolConnection<Sqlite>,
		new_name: &str,
	) -> Result<DbMacro, DatabaseError> {
		sqlx::query!("UPDATE macros SET name = ? WHERE id = ?", new_name, self.id,)
			.execute(conn.deref_mut())
			.await?;

		DbMacro::get(conn, self.id)
			.await?
			.ok_or(DatabaseError::UpdateGetError)
	}

	pub async fn set_actions(
		&self,
		conn: &mut PoolConnection<Sqlite>,
		new_actions: &[RequestMacroAction],
	) -> Result<(), DatabaseError> {
		sqlx::query!("DELETE FROM macro_actions WHERE macro_id = ?", self.id)
			.execute(conn.deref_mut())
			.await?;

		for new_action in new_actions {
			let controller = DbController::get_by_uid(conn, &new_action.relay.controller_id)
				.await?
				.ok_or(DatabaseError::NotFound)?;
			let relay =
				DbRelay::get_by_controller_and_num(conn, &controller, new_action.relay.number)
					.await?
					.ok_or(DatabaseError::NotFound)?;

			let schedule = DbSchedule::get_by_uid(conn, &new_action.schedule.id)
				.await?
				.ok_or(DatabaseError::NotFound)?;

			DbMacroAction::create(conn, self, &relay, &schedule, new_action.weekday).await?;
		}
		Ok(())
	}

	pub async fn get_actions(
		&self,
		conn: &mut PoolConnection<Sqlite>,
	) -> Result<Vec<DbMacroAction>, DatabaseError> {
		sqlx::query_as!(
			DbMacroAction,
			"SELECT * FROM macro_actions WHERE macro_id = ?",
			self.id
		)
		.fetch_all(conn.deref_mut())
		.await
		.map_err(DatabaseError::from)
	}

	pub async fn get_actions_weekday(
		&self,
		conn: &mut PoolConnection<Sqlite>,
		weekday: i64,
	) -> Result<Vec<DbMacroAction>, DatabaseError> {
		sqlx::query_as!(
			DbMacroAction,
			"SELECT * FROM macro_actions WHERE macro_id = ? AND weekday = ?",
			self.id,
			weekday
		)
		.fetch_all(conn.deref_mut())
		.await
		.map_err(DatabaseError::from)
	}
}