use std::time::Instant;

use chrono::NaiveTime;
use futures::executor::block_on;
use serde_derive::{Deserialize, Serialize};
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;

use crate::db::{DbController, DbJunctionRelaySchedule, DbRelay, DbSchedule};
use crate::errors::DatabaseError;
use crate::models::FromDbModel;
use crate::types::EmgauwaUid;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Relay {
	#[serde(flatten)]
	pub r: DbRelay,
	pub controller: DbController,
	pub controller_id: EmgauwaUid,
	pub schedules: Vec<DbSchedule>,
	pub active_schedule: DbSchedule,
	pub is_on: Option<bool>,
	pub tags: Vec<String>,

	// for internal use only.
	#[serde(skip)]
	pub pulsing: Option<Instant>,
}


impl FromDbModel for Relay {
	type DbModel = DbRelay;
	type DbModelCache = DbController;

	fn from_db_model(
		conn: &mut PoolConnection<Sqlite>,
		db_model: Self::DbModel,
	) -> Result<Self, DatabaseError> {
		let cache = block_on(db_model.get_controller(conn))?;
		Self::from_db_model_cache(conn, db_model, cache)
	}

	fn from_db_model_cache(
		conn: &mut PoolConnection<Sqlite>,
		db_model: Self::DbModel,
		cache: Self::DbModelCache,
	) -> Result<Self, DatabaseError> {
		let tags = block_on(db_model.get_tags(conn))?;
		let controller_id = cache.uid.clone();

		let schedules = block_on(DbJunctionRelaySchedule::get_schedules(conn, &db_model))?;
		let active_schedule = block_on(db_model.get_active_schedule(conn))?;

		let is_on = None;

		Ok(Relay {
			r: db_model,
			controller: cache,
			controller_id,
			schedules,
			active_schedule,
			is_on,
			tags,
			pulsing: None,
		})
	}
}

impl Relay {
	pub fn reload(&mut self, conn: &mut PoolConnection<Sqlite>) -> Result<(), DatabaseError> {
		self.r = block_on(self.r.reload(conn))?;
		self.schedules = block_on(DbJunctionRelaySchedule::get_schedules(conn, &self.r))?;
		self.reload_active_schedule(conn)?;

		Ok(())
	}

	pub fn reload_active_schedule(
		&mut self,
		conn: &mut PoolConnection<Sqlite>,
	) -> Result<(), DatabaseError> {
		self.active_schedule = block_on(self.r.get_active_schedule(conn))?;
		Ok(())
	}

	pub fn is_on(&self, now: &NaiveTime) -> bool {
		self.active_schedule.is_on(now)
	}

	pub fn get_next_time(&self, now: &NaiveTime) -> Option<NaiveTime> {
		self.active_schedule.get_next_time(now)
	}

	pub fn check_pulsing(&mut self, now: &Instant) -> Option<Instant> {
		match self.pulsing {
			Some(dur_instant) => {
				if dur_instant.lt(now) {
					self.pulsing = None;
					None
				} else {
					Some(dur_instant)
				}
			}
			None => None,
		}
	}
}