use crate::{
    domain::sql_tables::{DbQueryBuilder, Pool},
    infra::jwt_sql_tables::{JwtRefreshStorage, JwtStorage},
};
use actix::prelude::*;
use chrono::Local;
use cron::Schedule;
use sea_query::{Expr, Query};
use std::{str::FromStr, time::Duration};
use tracing::{debug, error, info, instrument};

// Define actor
pub struct Scheduler {
    schedule: Schedule,
    sql_pool: Pool,
}

// Provide Actor implementation for our actor
impl Actor for Scheduler {
    type Context = Context<Self>;

    fn started(&mut self, context: &mut Context<Self>) {
        info!("DB Cleanup Cron started");

        context.run_later(self.duration_until_next(), move |this, ctx| {
            this.schedule_task(ctx)
        });
    }

    fn stopped(&mut self, _ctx: &mut Context<Self>) {
        info!("DB Cleanup stopped");
    }
}

impl Scheduler {
    pub fn new(cron_expression: &str, sql_pool: Pool) -> Self {
        let schedule = Schedule::from_str(cron_expression).unwrap();
        Self { schedule, sql_pool }
    }

    fn schedule_task(&self, ctx: &mut Context<Self>) {
        let future = actix::fut::wrap_future::<_, Self>(Self::cleanup_db(self.sql_pool.clone()));
        ctx.spawn(future);

        ctx.run_later(self.duration_until_next(), move |this, ctx| {
            this.schedule_task(ctx)
        });
    }

    #[instrument(skip_all)]
    async fn cleanup_db(sql_pool: Pool) {
        info!("Cleaning DB");
        let query = Query::delete()
            .from_table(JwtRefreshStorage::Table)
            .and_where(Expr::col(JwtRefreshStorage::ExpiryDate).lt(Local::now().naive_utc()))
            .to_string(DbQueryBuilder {});
        debug!(%query);
        if let Err(e) = sqlx::query(&query).execute(&sql_pool).await {
            error!("DB error while cleaning up JWT refresh tokens: {}", e);
        };
        if let Err(e) = sqlx::query(
            &Query::delete()
                .from_table(JwtStorage::Table)
                .and_where(Expr::col(JwtStorage::ExpiryDate).lt(Local::now().naive_utc()))
                .to_string(DbQueryBuilder {}),
        )
        .execute(&sql_pool)
        .await
        {
            error!("DB error while cleaning up JWT storage: {}", e);
        };
        info!("DB cleaned!");
    }

    fn duration_until_next(&self) -> Duration {
        let now = Local::now();
        let next = self.schedule.upcoming(Local).next().unwrap();
        let duration_until = next.signed_duration_since(now);
        duration_until.to_std().unwrap()
    }
}
