use deadpool_redis::cluster::Pool as RedisPool;
use parsefile::{Pipeline, TriggerEvent};

use crate::{
    bare::GitBare,
    errors::GitError,
    sync::{HookTask, TaskType},
};

const PIPELINE_FILE: &str = "pipeline.yaml";
#[derive(Debug)]
pub enum CiCheckOutcome {
    Enqueued,
    NoPipelineFile,
    NotTriggered,
}
fn ci_queue_keys(repo_id: uuid::Uuid) -> (String, String) {
    let hash_tag = format!("{{ci:{}}}", repo_id);
    (
        format!("{}:pending", hash_tag),
        format!("{}:processing", hash_tag),
    )
}
pub async fn check_and_enqueue(
    bare: &GitBare,
    repo_id: uuid::Uuid,
    event: &TriggerEvent,
    redis_pool: &RedisPool,
) -> Result<CiCheckOutcome, GitError> {
    let output = bare.git_command_trusted_stdout(vec![
        "show".to_string(),
        format!("HEAD:{}", PIPELINE_FILE),
    ]);

    let content = match output {
        Ok(c) => c,
        Err(_) => return Ok(CiCheckOutcome::NoPipelineFile),
    };

    let pipeline = parsefile::parse_from_str(&content).map_err(|e| {
        GitError::Internal(format!("failed to parse {}: {}", PIPELINE_FILE, e))
    })?;

    if !pipeline.should_run(event) {
        return Ok(CiCheckOutcome::NotTriggered);
    }

    enqueue_ci_task(repo_id, event, &pipeline, redis_pool)
        .await
        .map_err(|e| {
            GitError::Internal(format!("failed to enqueue CI task: {}", e))
        })?;

    Ok(CiCheckOutcome::Enqueued)
}

async fn enqueue_ci_task(
    repo_id: uuid::Uuid,
    event: &TriggerEvent,
    pipeline: &Pipeline,
    redis_pool: &RedisPool,
) -> Result<(), String> {
    let hook_task = HookTask {
        id: uuid::Uuid::new_v4().to_string(),
        repo_id: repo_id.to_string(),
        task_type: TaskType::Sync,
        payload: serde_json::json!({
            "ci": true,
            "pipeline_name": pipeline.name,
            "trigger": event_variant_name(event),
        }),
        created_at: chrono::Utc::now(),
        retry_count: 0,
    };

    let task_json = serde_json::to_string(&hook_task)
        .map_err(|e| format!("serialize error: {}", e))?;

    let (pending_key, _) = ci_queue_keys(repo_id);

    let redis = redis_pool
        .get()
        .await
        .map_err(|e| format!("redis pool: {}", e))?;
    let mut conn: deadpool_redis::cluster::Connection = redis;

    redis::cmd("LPUSH")
        .arg(&pending_key)
        .arg(&task_json)
        .query_async::<()>(&mut conn)
        .await
        .map_err(|e| format!("LPUSH error: {}", e))?;

    tracing::info!(
        repo_id = %repo_id,
        pipeline = %pipeline.name,
        trigger = %event_variant_name(event),
        "CI task enqueued"
    );

    Ok(())
}

fn event_variant_name(event: &TriggerEvent) -> &'static str {
    match event {
        TriggerEvent::PushBranch(_) => "push_branch",
        TriggerEvent::PushTag(_) => "push_tag",
        TriggerEvent::PullRequest { .. } => "pull_request",
    }
}
pub async fn poll_ci_task_for_repo(
    redis_pool: &RedisPool,
    repo_id: uuid::Uuid,
    block_timeout_secs: usize,
) -> Option<String> {
    let (pending_key, processing_key) = ci_queue_keys(repo_id);

    let redis = redis_pool.get().await.ok()?;
    let mut conn: deadpool_redis::cluster::Connection = redis;

    redis::cmd("BLMOVE")
        .arg(&pending_key)
        .arg(&processing_key)
        .arg("RIGHT")
        .arg("LEFT")
        .arg(block_timeout_secs)
        .query_async::<Option<String>>(&mut conn)
        .await
        .ok()
        .flatten()
}
pub async fn ack_ci_task(
    redis_pool: &RedisPool,
    repo_id: uuid::Uuid,
    task_json: &str,
) {
    let (_, processing_key) = ci_queue_keys(repo_id);

    let redis = match redis_pool.get().await {
        Ok(c) => c,
        Err(e) => {
            tracing::warn!(error = %e, "CI ack: failed to get redis connection");
            return;
        }
    };
    let mut conn: deadpool_redis::cluster::Connection = redis;

    if let Err(e) = redis::cmd("LREM")
        .arg(&processing_key)
        .arg(1)
        .arg(task_json)
        .query_async::<()>(&mut conn)
        .await
    {
        tracing::warn!(error = %e, "CI ack: LREM failed");
    }
}
