use crate::db::errors::DatabaseError;
use crate::db::{DbController, DbRelay, DbSchedule};
use crate::types::ControllerUid;
use futures::executor;
use serde_derive::{Deserialize, Serialize};
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;

pub trait FromDbModel {
	type DbModel: Clone;

	fn from_db_model(
		conn: &mut PoolConnection<Sqlite>,
		db_model: Self::DbModel,
	) -> Result<Self, DatabaseError>
	where
		Self: Sized;
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Schedule {
	#[serde(flatten)]
	pub s: DbSchedule,
	pub tags: Vec<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Relay {
	#[serde(flatten)]
	pub r: DbRelay,
	pub controller: DbController,
	pub controller_id: ControllerUid,
	pub tags: Vec<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Controller {
	#[serde(flatten)]
	pub c: DbController,
	pub relays: Vec<Relay>,
}

impl FromDbModel for Schedule {
	type DbModel = DbSchedule;

	fn from_db_model(
		conn: &mut PoolConnection<Sqlite>,
		db_model: Self::DbModel,
	) -> Result<Self, DatabaseError> {
		let schedule = db_model.clone();
		let tags = executor::block_on(schedule.get_tags(conn))?;

		Ok(Schedule { s: schedule, tags })
	}
}

impl FromDbModel for Relay {
	type DbModel = DbRelay;

	fn from_db_model(
		conn: &mut PoolConnection<Sqlite>,
		db_model: Self::DbModel,
	) -> Result<Self, DatabaseError> {
		let relay = db_model.clone();
		let controller = executor::block_on(relay.get_controller(conn))?;
		let controller_id = controller.uid.clone();
		let tags = executor::block_on(relay.get_tags(conn))?;

		Ok(Relay {
			r: relay,
			controller,
			controller_id,
			tags,
		})
	}
}

impl FromDbModel for Controller {
	type DbModel = DbController;

	fn from_db_model(
		conn: &mut PoolConnection<Sqlite>,
		db_model: Self::DbModel,
	) -> Result<Self, DatabaseError> {
		let relays_db = executor::block_on(db_model.get_relays(conn))?;
		let relays = convert_db_list(conn, relays_db)?;
		Ok(Controller {
			c: db_model,
			relays,
		})
	}
}

pub fn convert_db_list<T: FromDbModel>(
	conn: &mut PoolConnection<Sqlite>,
	db_models: Vec<T::DbModel>,
) -> Result<Vec<T>, DatabaseError> {
	let mut result: Vec<T> = Vec::new();
	db_models.into_iter().try_for_each(|s| {
		let new = T::from_db_model(conn, s);
		match new {
			Ok(new) => {
				result.push(new);
				Ok(())
			}
			Err(e) => Err(e),
		}
	})?;
	Ok(result)
}