use anyhow::Result;
use axum::http::{HeaderValue, header::CONTENT_SECURITY_POLICY};
use chrono::{TimeDelta, Utc};
use clap::{Parser, Subcommand};
use tokio::time::sleep;
use tower::ServiceBuilder;
use tower_http::{
    compression::CompressionLayer, set_header::response::SetResponseHeaderLayer, trace::TraceLayer,
};

use crate::{app::App, routes::new_router, worker::run_worker};

#[derive(Parser)]
#[command(version, about, long_about = None)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Commands,
}

#[derive(Parser)]
pub struct WorkerArgs {
    /// Loop the every n seconds instead of exiting after execution
    #[arg(long)]
    auto_loop_seconds: Option<u32>,
}

#[derive(Subcommand)]
pub enum Commands {
    /// Run web server
    Serve,
    /// Run background worker
    Worker(WorkerArgs),
}

pub async fn serve_command(state: App) -> Result<()> {
    let router = new_router(state.clone()).layer(
        ServiceBuilder::new()
            .layer(TraceLayer::new_for_http())
            .layer(CompressionLayer::new())
            .layer(SetResponseHeaderLayer::if_not_present(
                CONTENT_SECURITY_POLICY,
                HeaderValue::from_static("frame-ancestors 'none'"),
            )),
    );

    let listener =
        tokio::net::TcpListener::bind((state.settings.host.clone(), state.settings.port))
            .await
            .unwrap();
    tracing::info!(
        "App running at http://{}:{}{}",
        state.settings.host,
        state.settings.port,
        state.settings.root_path
    );

    axum::serve(listener, router).await?;

    Ok(())
}

pub async fn worker_command(args: &WorkerArgs, state: App) -> Result<()> {
    if let Some(loop_seconds) = args.auto_loop_seconds {
        let loop_delta = TimeDelta::seconds(i64::from(loop_seconds));
        loop {
            let t_next_loop = Utc::now() + loop_delta;

            if let Err(err) = run_worker(state.clone()).await {
                tracing::error!("{}", err)
            }

            let sleep_delta = t_next_loop - Utc::now();
            match sleep_delta.to_std() {
                Ok(duration) => {
                    sleep(duration).await;
                }
                Err(_) => { /* sleep_delta was < 0, so don't sleep */ }
            }
        }
    } else {
        run_worker(state).await
    }
}
