use std::ops::DerefMut;

use sqlx::pool::PoolConnection;
use sqlx::Sqlite;

use crate::db::{DbRelay, DbSchedule};
use crate::errors::DatabaseError;
use crate::types::Weekday;

pub struct DbJunctionRelaySchedule {
	pub id: i64,
	pub weekday: Weekday,
	pub relay_id: i64,
	pub schedule_id: i64,
}

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

	pub async fn get_junction_by_relay_and_weekday(
		conn: &mut PoolConnection<Sqlite>,
		relay: &DbRelay,
		weekday: Weekday,
	) -> Result<Option<DbJunctionRelaySchedule>, DatabaseError> {
		sqlx::query_as!(
			DbJunctionRelaySchedule,
			"SELECT * FROM junction_relay_schedule WHERE relay_id = ? AND weekday = ?",
			relay.id,
			weekday
		)
		.fetch_optional(conn.deref_mut())
		.await
		.map_err(DatabaseError::from)
	}

	pub async fn get_relays(
		conn: &mut PoolConnection<Sqlite>,
		schedule: &DbSchedule,
	) -> Result<Vec<DbRelay>, DatabaseError> {
		sqlx::query_as!(
			DbRelay,
			r#"SELECT relays.* FROM relays INNER JOIN junction_relay_schedule
			ON junction_relay_schedule.relay_id = relays.id
			WHERE junction_relay_schedule.schedule_id = ?
			ORDER BY junction_relay_schedule.weekday"#,
			schedule.id
		)
		.fetch_all(conn.deref_mut())
		.await
		.map_err(DatabaseError::from)
	}

	pub async fn get_schedule(
		conn: &mut PoolConnection<Sqlite>,
		relay: &DbRelay,
		weekday: Weekday,
	) -> Result<Option<DbSchedule>, DatabaseError> {
		sqlx::query_as!(
			DbSchedule,
			r#"SELECT schedules.* FROM schedules INNER JOIN junction_relay_schedule
			ON junction_relay_schedule.schedule_id = schedules.id
			WHERE junction_relay_schedule.relay_id = ? AND junction_relay_schedule.weekday = ?"#,
			relay.id,
			weekday
		)
		.fetch_optional(conn.deref_mut())
		.await
		.map_err(DatabaseError::from)
	}

	pub async fn get_schedules(
		conn: &mut PoolConnection<Sqlite>,
		relay: &DbRelay,
	) -> Result<Vec<DbSchedule>, DatabaseError> {
		sqlx::query_as!(
			DbSchedule,
			r#"SELECT schedules.* FROM schedules INNER JOIN junction_relay_schedule
			ON junction_relay_schedule.schedule_id = schedules.id
			WHERE junction_relay_schedule.relay_id = ?
			ORDER BY junction_relay_schedule.weekday"#,
			relay.id
		)
		.fetch_all(conn.deref_mut())
		.await
		.map_err(DatabaseError::from)
	}

	pub async fn set_schedule(
		conn: &mut PoolConnection<Sqlite>,
		relay: &DbRelay,
		schedule: &DbSchedule,
		weekday: Weekday,
	) -> Result<DbJunctionRelaySchedule, DatabaseError> {
		match Self::get_junction_by_relay_and_weekday(conn, relay, weekday).await? {
			None => sqlx::query_as!(
				DbJunctionRelaySchedule,
				"INSERT INTO junction_relay_schedule (weekday, relay_id, schedule_id) VALUES (?, ?, ?) RETURNING *",
				weekday,
				relay.id,
				schedule.id
			)
			.fetch_optional(conn.deref_mut())
			.await?
			.ok_or(DatabaseError::InsertGetError),

			Some(junction) => {
				sqlx::query!(
                    "UPDATE junction_relay_schedule SET weekday = ?, relay_id = ?, schedule_id= ? WHERE id = ?",
                    weekday,
                    relay.id,
                    schedule.id,
					junction.id
                )
					.execute(conn.deref_mut())
					.await?;

				Self::get(conn, junction.id)
					.await?
					.ok_or(DatabaseError::UpdateGetError)
			}
		}
	}

	pub async fn set_schedules(
		conn: &mut PoolConnection<Sqlite>,
		relay: &DbRelay,
		schedules: Vec<&DbSchedule>,
	) -> Result<(), DatabaseError> {
		for (weekday, schedule) in schedules.iter().enumerate() {
			Self::set_schedule(conn, relay, schedule, weekday as Weekday).await?;
		}
		Ok(())
	}
}