use clap::Parser;
use config::AppConfig;
use db::cache::AppCache;
use db::database::AppDatabase;
use git::hook::GitServiceHooks;
use slog::{Drain, OwnedKVList, Record};
use tokio::signal;
use tokio_util::sync::CancellationToken;

mod args;

use args::HookArgs;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 1. Load configuration
    let cfg = AppConfig::load();

    // 2. Init slog logging
    let log_level = cfg.log_level().unwrap_or_else(|_| "info".to_string());
    let log = build_slog_logger(&log_level);

    // 3. Connect to database
    let db = AppDatabase::init(&cfg).await?;
    slog::info!(log, "database connected");

    // 4. Connect to Redis cache (also provides the cluster pool for hook queue)
    let cache = AppCache::init(&cfg).await?;
    slog::info!(log, "cache connected");

    // 5. Parse CLI args
    let args = HookArgs::parse();

    slog::info!(log, "git-hook worker starting";
        "worker_id" => %args.worker_id.unwrap_or_else(|| "default".to_string())
    );

    // 5. Build HTTP client for webhook delivery
    let http = reqwest::Client::builder()
        .user_agent("Code-Git-Hook/1.0")
        .build()
        .unwrap_or_else(|_| reqwest::Client::new());

    // 6. Build and run git hook service
    let hooks = GitServiceHooks::new(
        db,
        cache.clone(),
        cache.redis_pool().clone(),
        log.clone(),
        cfg,
        std::sync::Arc::new(http),
    );

    let cancel = CancellationToken::new();
    let cancel_clone = cancel.clone();

    // Spawn signal handler
    let log_clone = log.clone();
    tokio::spawn(async move {
        let ctrl_c = async {
            signal::ctrl_c()
                .await
                .expect("failed to install CTRL+C handler");
        };

        #[cfg(unix)]
        let term = async {
            use tokio::signal::unix::{SignalKind, signal};
            let mut sig =
                signal(SignalKind::terminate()).expect("failed to install SIGTERM handler");
            sig.recv().await;
        };

        #[cfg(not(unix))]
        let term = std::future::pending::<()>();

        tokio::select! {
            _ = ctrl_c => {
                slog::info!(log_clone, "received SIGINT, initiating shutdown");
            }
            _ = term => {
                slog::info!(log_clone, "received SIGTERM, initiating shutdown");
            }
        }
        cancel_clone.cancel();
    });

    hooks.run(cancel).await?;

    slog::info!(log, "git-hook worker stopped");
    Ok(())
}

fn build_slog_logger(level: &str) -> slog::Logger {
    let level_filter = match level {
        "trace" => 0usize,
        "debug" => 1usize,
        "info" => 2usize,
        "warn" => 3usize,
        "error" => 4usize,
        _ => 2usize,
    };

    struct StderrDrain(usize);

    impl Drain for StderrDrain {
        type Ok = ();
        type Err = ();
        #[inline]
        fn log(&self, record: &Record, _logger: &OwnedKVList) -> Result<(), ()> {
            let slog_level = match record.level() {
                slog::Level::Trace => 0,
                slog::Level::Debug => 1,
                slog::Level::Info => 2,
                slog::Level::Warning => 3,
                slog::Level::Error => 4,
                slog::Level::Critical => 5,
            };
            if slog_level < self.0 {
                return Ok(());
            }
            let _ = eprintln!(
                "{} [{}] {}:{} - {}",
                chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ"),
                record.level().to_string(),
                record
                    .file()
                    .rsplit_once('/')
                    .map(|(_, s)| s)
                    .unwrap_or(record.file()),
                record.line(),
                record.msg(),
            );
            Ok(())
        }
    }

    let drain = StderrDrain(level_filter);
    let drain = std::sync::Mutex::new(drain);
    let drain = slog::Fuse::new(drain);
    slog::Logger::root(drain, slog::o!())
}
