use std::sync::Arc;
use std::time::{Duration, Instant};

use actix::{Actor, Context, Handler, Message};
use emgauwa_common::constants;
use emgauwa_common::drivers::RelayDriver;
use emgauwa_common::errors::EmgauwaError;
use emgauwa_common::models::Controller;
use emgauwa_common::types::RelayStates;
use futures::executor::block_on;
use sqlx::{Pool, Sqlite};
use tokio::sync::Notify;

use crate::settings::Settings;

#[derive(Message)]
#[rtype(result = "Result<(), EmgauwaError>")]
pub struct Reload {}

#[derive(Message)]
#[rtype(result = "()")]
pub struct UpdateRelayStates {
	pub relay_states: RelayStates,
}

#[derive(Message)]
#[rtype(result = "Result<(), EmgauwaError>")]
pub struct RelayPulse {
	pub relay_number: i64,
	pub duration: Option<u32>,
}

#[derive(Message)]
#[rtype(result = "Controller")]
pub struct GetThis {}

#[derive(Message)]
#[rtype(result = "Arc<Notify>")]
pub struct GetControllerNotifier {}

#[derive(Message)]
#[rtype(result = "Arc<Notify>")]
pub struct GetRelayNotifier {}

pub struct AppState {
	pub pool: Pool<Sqlite>,
	pub this: Controller,
	pub settings: Settings,
	pub drivers: Vec<Box<dyn RelayDriver>>,
	pub controller_notifier: Arc<Notify>,
	pub relay_notifier: Arc<Notify>,
}

impl AppState {
	pub fn new(
		pool: Pool<Sqlite>,
		this: Controller,
		settings: Settings,
		drivers: Vec<Box<dyn RelayDriver>>,
	) -> AppState {
		AppState {
			pool,
			this,
			settings,
			drivers,
			controller_notifier: Arc::new(Notify::new()),
			relay_notifier: Arc::new(Notify::new()),
		}
	}

	pub fn notify_controller_change(&self) {
		self.controller_notifier.notify_one();
	}

	pub fn notify_relay_change(&self) {
		self.relay_notifier.notify_one();
	}
}

impl Actor for AppState {
	type Context = Context<Self>;
}

impl Handler<Reload> for AppState {
	type Result = Result<(), EmgauwaError>;

	fn handle(&mut self, _msg: Reload, _ctx: &mut Self::Context) -> Self::Result {
		log::debug!("Reloading controller");
		let mut pool_conn = block_on(self.pool.acquire())?;

		self.this.reload(&mut pool_conn)?;

		self.notify_controller_change();

		Ok(())
	}
}

impl Handler<UpdateRelayStates> for AppState {
	type Result = ();

	fn handle(&mut self, msg: UpdateRelayStates, _ctx: &mut Self::Context) -> Self::Result {
		self.this.apply_relay_states(&msg.relay_states);
		self.drivers
			.iter_mut()
			.zip(msg.relay_states.iter())
			.for_each(|(driver, state)| {
				if let Err(e) = driver.set(state.unwrap_or(false)) {
					log::error!("Error setting relay: {}", e);
				}
			});

		self.notify_relay_change();
	}
}

impl Handler<RelayPulse> for AppState {
	type Result = Result<(), EmgauwaError>;

	fn handle(&mut self, msg: RelayPulse, _ctx: &mut Self::Context) -> Self::Result {
		let relay_num = msg.relay_number;

		let duration = Duration::from_secs(
			match msg.duration {
				None => {
					self.settings
						.get_relay(relay_num)
						.ok_or(EmgauwaError::Other(String::from(
							"Relay not found in settings",
						)))?
						.pulse
				}
				Some(dur) => Some(dur as u64),
			}
			.unwrap_or(constants::RELAY_PULSE_DURATION),
		);
		let now = Instant::now();
		let until = now + duration;

		self.this.relay_pulse(relay_num, until)?;
		log::debug!(
			"Pulsing relay {} for {} seconds until {:?}",
			relay_num,
			duration.as_secs(),
			until
		);
		Ok(())
	}
}

impl Handler<GetThis> for AppState {
	type Result = Controller;

	fn handle(&mut self, _msg: GetThis, _ctx: &mut Self::Context) -> Self::Result {
		self.this.clone()
	}
}

impl Handler<GetControllerNotifier> for AppState {
	type Result = Arc<Notify>;

	fn handle(&mut self, _msg: GetControllerNotifier, _ctx: &mut Self::Context) -> Self::Result {
		Arc::clone(&self.controller_notifier)
	}
}

impl Handler<GetRelayNotifier> for AppState {
	type Result = Arc<Notify>;

	fn handle(&mut self, _msg: GetRelayNotifier, _ctx: &mut Self::Context) -> Self::Result {
		Arc::clone(&self.relay_notifier)
	}
}