use log::{info, trace};
use sqlx::migrate::Migrator;
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
use sqlx::{Pool, Sqlite};
use std::str::FromStr;

use crate::db::errors::DatabaseError;
use crate::db::model_utils::Period;
use crate::types::ScheduleUid;

mod controllers;
pub mod errors;
mod model_utils;
mod relays;
mod schedules;
mod tag;

pub use controllers::DbController;
pub use relays::DbRelay;
pub use schedules::{DbPeriods, DbSchedule};
pub use tag::DbTag;

static MIGRATOR: Migrator = sqlx::migrate!("../migrations"); // defaults to "./migrations"

pub async fn run_migrations(pool: &Pool<Sqlite>) {
	info!("Running migrations");
	MIGRATOR.run(pool).await.expect("Failed to run migrations.");
}

async fn init_schedule(
	pool: &Pool<Sqlite>,
	uid: &ScheduleUid,
	name: &str,
	periods: DbPeriods,
) -> Result<(), DatabaseError> {
	trace!("Initializing schedule {:?}", name);
	match DbSchedule::get_by_uid(&mut pool.acquire().await.unwrap(), uid).await {
		Ok(_) => Ok(()),
		Err(err) => match err {
			DatabaseError::NotFound => {
				trace!("Schedule {:?} not found, inserting", name);
				sqlx::query_as!(
					DbSchedule,
					"INSERT INTO schedules (uid, name, periods) VALUES (?, ?, ?) RETURNING *",
					uid,
					name,
					periods,
				)
				.fetch_optional(pool)
				.await?
				.ok_or(DatabaseError::InsertGetError)
				.map(|_| ())
			}
			_ => Err(err),
		},
	}
}

pub async fn init(db: &str) -> Pool<Sqlite> {
	let options = SqliteConnectOptions::from_str(db)
		.expect("Error parsing database path")
		.create_if_missing(true);

	let pool: Pool<Sqlite> = SqlitePoolOptions::new()
		.acquire_timeout(std::time::Duration::from_secs(1))
		.max_connections(5)
		.connect_with(options)
		.await
		.expect("Error connecting to database");

	run_migrations(&pool).await;

	init_schedule(&pool, &ScheduleUid::Off, "Off", DbPeriods(vec![]))
		.await
		.expect("Error initializing schedule Off");

	init_schedule(
		&pool,
		&ScheduleUid::On,
		"On",
		DbPeriods(vec![Period::new_on()]),
	)
	.await
	.expect("Error initializing schedule On");

	pool
}