use std::sync::Arc;

use ::agent::task::service::TaskService;
use avatar::AppAvatar;
use config::AppConfig;
use db::cache::AppCache;
use db::database::AppDatabase;
use email::AppEmail;
use queue::{
    start_email_worker, EmailEnvelope, EmailSendFn, EmailSendFut, GetRedis, MessageProducer, RedisFuture,
    RedisPubSub,
};
use room::metrics::RoomMetrics;
use room::RoomService;
use serde::{Deserialize, Serialize};
use slog::{Drain, OwnedKVList, Record};
use utoipa::ToSchema;
use ws_token::WsTokenService;

#[derive(Clone)]
pub struct AppService {
    pub db: AppDatabase,
    pub config: AppConfig,
    pub cache: AppCache,
    pub email: AppEmail,
    pub logs: slog::Logger,
    pub avatar: AppAvatar,
    pub room: RoomService,
    pub ws_token: Arc<WsTokenService>,
    pub queue_producer: MessageProducer,
}

impl AppService {
    pub async fn start_room_workers(
        &self,
        shutdown_rx: tokio::sync::broadcast::Receiver<()>,
        log: slog::Logger,
    ) -> anyhow::Result<()> {
        self.room.start_workers(shutdown_rx, log).await
    }

    pub 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!())
    }

    pub async fn new(config: AppConfig) -> anyhow::Result<Self> {
        let db = AppDatabase::init(&config).await?;
        let cache = AppCache::init(&config).await?;
        let email = AppEmail::init(&config).await?;
        let avatar = AppAvatar::init(&config).await?;

        let log_level = config.log_level().unwrap_or_else(|_| "info".to_string());
        let logs = Self::build_slog_logger(&log_level);

        // Build get_redis closure for MessageProducer
        let get_redis: Arc<
            dyn Fn() -> tokio::task::JoinHandle<anyhow::Result<deadpool_redis::cluster::Connection>>
                + Send
                + Sync,
        > = Arc::new({
            let pool = cache.redis_pool().clone();
            move || {
                let pool = pool.clone();
                tokio::spawn(async move { pool.get().await.map_err(|e| anyhow::anyhow!("{}", e)) })
            }
        });

        let redis_pubsub = Some(RedisPubSub {
            get_redis: get_redis.clone(),
            log: logs.clone(),
        });

        let message_producer =
            MessageProducer::new(get_redis.clone(), redis_pubsub.clone(), 10000, logs.clone());

        // Build RoomService
        let task_service = Arc::new(TaskService::new(db.clone()));
        let room_metrics = Arc::new(RoomMetrics::default());
        let room_manager = Arc::new(room::connection::RoomConnectionManager::new(
            room_metrics.clone(),
        ));

        let redis_url = config
            .redis_urls()
            .ok()
            .and_then(|urls| urls.first().cloned())
            .unwrap_or_else(|| "redis://127.0.0.1:6379".to_string());

        let room = RoomService::new(
            db.clone(),
            cache.clone(),
            message_producer.clone(),
            room_manager,
            redis_url,
            None,
            Some(task_service.clone()),
            logs.clone(),
            None,
        );

        // Build WsTokenService
        let ws_token = Arc::new(WsTokenService::new(get_redis.clone()));

        Ok(Self {
            db,
            config,
            cache,
            email,
            logs,
            avatar,
            room,
            ws_token,
            queue_producer: message_producer,
        })
    }

    pub async fn start_email_workers(
        &self,
        shutdown_rx: tokio::sync::broadcast::Receiver<()>,
    ) -> anyhow::Result<()> {
        let get_redis_fn = self.queue_producer.get_redis.clone();
        let get_redis: GetRedis = Arc::new(move || -> RedisFuture {
            let get_redis_fn = get_redis_fn.clone();
            Box::pin(async move { get_redis_fn().await? })
        });

        let email = self.email.clone();
        let logs = self.logs.clone();
        let send_fn: EmailSendFn = Arc::new(move |envelopes: Vec<EmailEnvelope>| -> EmailSendFut {
            let email = email.clone();
            let logs = logs.clone();
            Box::pin(async move {
                for envelope in envelopes {
                    let to = envelope.to.clone();
                    let msg = email::EmailMessage {
                        to: envelope.to,
                        subject: envelope.subject,
                        body: envelope.body,
                    };
                    if let Err(e) = email.send(msg).await {
                        slog::error!(logs, "email send failed"; "to" => to, "error" => %e);
                    }
                }
                Ok(())
            })
        });

        start_email_worker(get_redis, send_fn, shutdown_rx, self.logs.clone()).await;
        Ok(())
    }
}

pub mod agent;
pub mod auth;
pub mod error;
pub mod git;
pub mod issue;
pub mod project;
pub mod pull_request;
pub mod search;
pub mod skill;
pub mod user;
pub mod utils;
pub mod workspace;
pub mod ws_token;

#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
pub struct Pager {
    pub page: i64,
    pub par_page: i64,
}
