use actix::Addr;
use actix_web::{delete, get, post, put, web, HttpResponse};
use emgauwa_lib::db::DbMacro;
use emgauwa_lib::errors::{DatabaseError, EmgauwaError};
use emgauwa_lib::models::{convert_db_list, FromDbModel, Macro, MacroAction, Relay};
use emgauwa_lib::types::{
	ControllerWsAction, EmgauwaUid, RequestMacroCreate, RequestMacroExecute, RequestMacroUpdate,
};
use itertools::Itertools;
use sqlx::{Pool, Sqlite};

use crate::app_state;
use crate::app_state::AppState;

#[get("/macros")]
pub async fn index(pool: web::Data<Pool<Sqlite>>) -> Result<HttpResponse, EmgauwaError> {
	let mut pool_conn = pool.acquire().await?;

	let db_macros = DbMacro::get_all(&mut pool_conn).await?;
	let macros: Vec<Macro> = convert_db_list(&mut pool_conn, db_macros)?;

	Ok(HttpResponse::Ok().json(macros))
}

#[get("/macros/{macro_id}")]
pub async fn show(
	pool: web::Data<Pool<Sqlite>>,
	path: web::Path<(String,)>,
) -> Result<HttpResponse, EmgauwaError> {
	let mut pool_conn = pool.acquire().await?;

	let (macro_uid,) = path.into_inner();
	let uid = EmgauwaUid::try_from(macro_uid.as_str())?;

	let db_macro = DbMacro::get_by_uid(&mut pool_conn, &uid)
		.await?
		.ok_or(DatabaseError::NotFound)?;

	let return_macro = Macro::from_db_model(&mut pool_conn, db_macro)?;
	Ok(HttpResponse::Ok().json(return_macro))
}

#[post("/macros")]
pub async fn add(
	pool: web::Data<Pool<Sqlite>>,
	data: web::Json<RequestMacroCreate>,
) -> Result<HttpResponse, EmgauwaError> {
	let mut pool_conn = pool.acquire().await?;

	let new_macro = DbMacro::create(&mut pool_conn, EmgauwaUid::default(), &data.name).await?;

	new_macro
		.set_actions(&mut pool_conn, data.actions.as_slice())
		.await?;

	let return_macro = Macro::from_db_model(&mut pool_conn, new_macro)?;
	Ok(HttpResponse::Created().json(return_macro))
}

#[put("/macros/{macro_id}")]
pub async fn update(
	pool: web::Data<Pool<Sqlite>>,
	path: web::Path<(String,)>,
	data: web::Json<RequestMacroUpdate>,
) -> Result<HttpResponse, EmgauwaError> {
	let mut pool_conn = pool.acquire().await?;

	let (macro_uid,) = path.into_inner();
	let uid = EmgauwaUid::try_from(macro_uid.as_str())?;

	let db_macro = DbMacro::get_by_uid(&mut pool_conn, &uid)
		.await?
		.ok_or(DatabaseError::NotFound)?;

	if let Some(name) = &data.name {
		db_macro.update(&mut pool_conn, name).await?;
	}

	if let Some(actions) = &data.actions {
		db_macro
			.set_actions(&mut pool_conn, actions.as_slice())
			.await?;
	}

	let return_macro = Macro::from_db_model(&mut pool_conn, db_macro)?;
	Ok(HttpResponse::Ok().json(return_macro))
}

#[delete("/macros/{macro_id}")]
pub async fn delete(
	pool: web::Data<Pool<Sqlite>>,
	path: web::Path<(String,)>,
) -> Result<HttpResponse, EmgauwaError> {
	let mut pool_conn = pool.acquire().await?;

	let (macro_uid,) = path.into_inner();
	let uid = EmgauwaUid::try_from(macro_uid.as_str())?;

	DbMacro::delete_by_uid(&mut pool_conn, uid).await?;
	Ok(HttpResponse::Ok().json("macro got deleted"))
}

#[put("/macros/{macro_id}/execute")]
pub async fn execute(
	pool: web::Data<Pool<Sqlite>>,
	app_state: web::Data<Addr<AppState>>,
	path: web::Path<(String,)>,
	query: web::Query<RequestMacroExecute>,
) -> Result<HttpResponse, EmgauwaError> {
	let mut pool_conn = pool.acquire().await?;

	let (macro_uid,) = path.into_inner();
	let uid = EmgauwaUid::try_from(macro_uid.as_str())?;

	let db_macro = DbMacro::get_by_uid(&mut pool_conn, &uid)
		.await?
		.ok_or(DatabaseError::NotFound)?;

	let actions_db = match query.weekday {
		None => db_macro.get_actions(&mut pool_conn).await?,
		Some(weekday) => {
			db_macro
				.get_actions_weekday(&mut pool_conn, weekday)
				.await?
		}
	};
	let mut actions: Vec<MacroAction> = convert_db_list(&mut pool_conn, actions_db)?;

	for action in &actions {
		action.execute(&mut pool_conn).await?;
	}

	let affected_controller_uids: Vec<EmgauwaUid> = actions
		.iter()
		.map(|action| action.relay.controller_id.clone())
		.unique()
		.collect();

	for controller_uid in affected_controller_uids {
		let mut affected_relays: Vec<Relay> = Vec::new();
		let mut affected_relay_ids: Vec<i64> = Vec::new();

		for action in actions.iter_mut() {
			if affected_relay_ids.contains(&action.relay.r.id) {
				continue;
			}
			action.relay.reload(&mut pool_conn)?;
			affected_relays.push(action.relay.clone());
			affected_relay_ids.push(action.relay.r.id);
		}

		app_state
			.send(app_state::Action {
				controller_uid,
				action: ControllerWsAction::Relays(affected_relays.clone()),
			})
			.await??;
	}

	Ok(HttpResponse::Ok().finish()) // TODO add a message?
}