use std::collections::HashMap;

use actix::{Actor, Context, Handler, Message, Recipient};
use emgauwa_lib::errors::EmgauwaError;
use emgauwa_lib::models::Controller;
use emgauwa_lib::types::{ControllerUid, ControllerWsAction};
use futures::executor::block_on;
use sqlx::{Pool, Sqlite};

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

#[derive(Message)]
#[rtype(result = "Result<(), EmgauwaError>")]
pub struct ConnectController {
	pub address: Recipient<ControllerWsAction>,
	pub controller: Controller,
}

#[derive(Message)]
#[rtype(result = "Result<(), EmgauwaError>")]
pub struct Action {
	pub controller_uid: ControllerUid,
	pub action: ControllerWsAction,
}

pub struct AppServer {
	pub pool: Pool<Sqlite>,
	pub connected_controllers: HashMap<ControllerUid, (Controller, Recipient<ControllerWsAction>)>,
}

impl AppServer {
	pub fn new(pool: Pool<Sqlite>) -> AppServer {
		AppServer {
			pool,
			connected_controllers: HashMap::new(),
		}
	}
}

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

impl Handler<DisconnectController> for AppServer {
	type Result = Result<(), EmgauwaError>;

	fn handle(&mut self, msg: DisconnectController, _ctx: &mut Self::Context) -> Self::Result {
		let mut pool_conn = block_on(self.pool.acquire())?;

		if let Some((controller, _)) = self.connected_controllers.remove(&msg.controller_uid) {
			if let Err(err) = block_on(controller.c.update_active(&mut pool_conn, false)) {
				log::error!(
					"Failed to mark controller {} as inactive: {:?}",
					controller.c.uid,
					err
				);
			}
		}
		Ok(())
	}
}

impl Handler<ConnectController> for AppServer {
	type Result = Result<(), EmgauwaError>;

	fn handle(&mut self, msg: ConnectController, _ctx: &mut Self::Context) -> Self::Result {
		self.connected_controllers
			.insert(msg.controller.c.uid.clone(), (msg.controller, msg.address));

		Ok(())
	}
}

impl Handler<Action> for AppServer {
	type Result = Result<(), EmgauwaError>;

	fn handle(&mut self, msg: Action, _ctx: &mut Self::Context) -> Self::Result {
		if let Some((_, address)) = self.connected_controllers.get(&msg.controller_uid) {
			block_on(address.send(msg.action))?
		} else {
			Err(EmgauwaError::Connection(msg.controller_uid))
		}
	}
}