use std::time::Duration;

use cache::{AppCache, AppCacheConfig};
use config::AppConfig;
use db::database::AppDatabase;
use deadpool_redis::{PoolConfig, Runtime, Timeouts, cluster::Config};

pub struct AppContext {
    pub config: AppConfig,
    pub db: AppDatabase,
    pub cache: AppCache,
    pub redis_pool: deadpool_redis::cluster::Pool,
}

impl AppContext {
    pub async fn init() -> anyhow::Result<Self> {
        let config = AppConfig::load();
        init_tracing(&config)?;

        tracing::info!("initializing database");
        let db = AppDatabase::init(&config).await?;

        tracing::info!("initializing cache");
        let cache_config = AppCacheConfig::try_from(&config)?;
        let cache = AppCache::init(cache_config).await?;

        tracing::info!("initializing redis pool");
        let redis_urls = config.redis_urls()?;
        let pool_size = config.redis_pool_size()?;
        let connect_timeout = config.redis_connect_timeout()?;
        let acquire_timeout = config.redis_acquire_timeout()?;

        let mut pool_config = PoolConfig::new(pool_size as usize);
        pool_config.timeouts = Timeouts {
            wait: Some(Duration::from_secs(acquire_timeout)),
            create: Some(Duration::from_secs(connect_timeout)),
            recycle: Some(Duration::from_secs(connect_timeout)),
        };

        let cfg = Config {
            urls: Some(redis_urls),
            connections: None,
            pool: Some(pool_config),
            read_from_replicas: false,
        };
        let redis_pool = cfg.create_pool(Some(Runtime::Tokio1))?;

        Ok(Self {
            config,
            db,
            cache,
            redis_pool,
        })
    }
}

fn init_tracing(config: &AppConfig) -> anyhow::Result<()> {
    let level = config.log_level()?;
    let filter = tracing_subscriber::EnvFilter::try_new(&level)?;
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        .with_target(false)
        .init();
    Ok(())
}
