use std::time::Instant;

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

use crate::db::DbController;
use crate::errors::{DatabaseError, EmgauwaError};
use crate::models::{convert_db_list_cache, FromDbModel, Relay};
use crate::types::RelayStates;

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

impl FromDbModel for Controller {
	type DbModel = DbController;
	type DbModelCache = Vec<Relay>;

	fn from_db_model(
		conn: &mut PoolConnection<Sqlite>,
		db_model: Self::DbModel,
	) -> Result<Self, DatabaseError> {
		let relays_db = block_on(db_model.get_relays(conn))?;
		let cache = convert_db_list_cache(conn, relays_db, db_model.clone())?;
		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> {
		Ok(Controller {
			c: db_model,
			relays: cache,
		})
	}
}

impl Controller {
	pub fn reload(&mut self, conn: &mut PoolConnection<Sqlite>) -> Result<(), EmgauwaError> {
		self.c = block_on(self.c.reload(conn))?;
		for relay in &mut self.relays {
			relay.reload(conn)?;
		}
		Ok(())
	}

	pub fn apply_relay_states(&mut self, relay_states: &RelayStates) {
		self.relays
			.iter_mut()
			.zip(relay_states.iter())
			.for_each(|(relay, is_on)| {
				relay.is_on = *is_on;
			});
	}

	pub fn get_relay_states(&self) -> RelayStates {
		self.relays.iter().map(|r| r.is_on).collect()
	}

	pub fn get_next_time(&self, now: &NaiveTime) -> Option<NaiveTime> {
		self.relays
			.iter()
			.filter_map(|r| r.active_schedule.get_next_time(now))
			.min()
	}

	pub fn relay_pulse(&mut self, relay_num: i64, until: Instant) -> Result<(), EmgauwaError> {
		let relay = self
			.relays
			.iter_mut()
			.find(|r| r.r.number == relay_num)
			.ok_or(EmgauwaError::Other(String::from("Relay not found")))?;

		relay.pulsing = Some(until);
		Ok(())
	}
}