use cache::AppCache;
use config::AppConfig;
use db::AppDatabase;
use deadpool_redis::cluster::Pool as RedisPool;
use email::AppEmail;
use error::AppError;
use serde::Deserialize;
use session::Session;
use storage::AppStorage;
use tonic::transport::Channel;
use uuid::Uuid;

pub mod agent;
pub mod ai;
pub mod auth;
pub mod error;
pub mod git;
pub mod issues;
pub mod pull_request;
pub mod user;
pub mod users;
pub mod workspace;
pub fn session_user(ctx: &Session) -> Result<Uuid, AppError> {
    ctx.user().ok_or(AppError::Unauthorized)
}

pub fn non_empty(value: String) -> Option<String> {
    if value.is_empty() { None } else { Some(value) }
}

pub fn constant_time_eq(a: &str, b: &str) -> bool {
    if a.len() != b.len() {
        return false;
    }
    a.bytes()
        .zip(b.bytes())
        .fold(0u8, |acc, (x, y)| acc | (x ^ y))
        == 0
}

#[derive(Debug, Clone, Deserialize, utoipa::IntoParams, utoipa::ToSchema)]
pub struct Pagination {
    pub offset: Option<u32>,
    pub limit: Option<u32>,
}

impl Pagination {
    pub fn offset(&self) -> u32 {
        self.offset.unwrap_or(0)
    }
    pub fn limit(&self) -> u32 {
        self.limit.unwrap_or(20).min(100)
    }
}

#[derive(Clone)]
pub struct AppService {
    pub db: AppDatabase,
    pub cache: AppCache,
    pub email: AppEmail,
    pub storage: AppStorage,
    pub config: AppConfig,
    pub git: Channel,
    pub redis_pool: RedisPool,
}
