use std::sync::Arc;

use anyhow::Result;
use tokio::sync::{watch, Semaphore};
use tracing::{debug, error, info, warn};

use crate::config::WorkflowState;
use crate::entities::task::Model as Task;
use robson_core::plugin::ProcessEventKind;
use robson_core::utils::truncate_log;

pub mod context;
pub mod implementation;
pub(crate) mod notification;
pub mod run_task;
pub(crate) mod setup;

pub use context::TaskExecutionContext;

/// Emit a `ProcessEvent` to the agent DB so the SensoriumLoop can route it back
/// to the originating gateway channel. No-op when the workflow was triggered from the CLI.
pub async fn emit_process_event(state: &WorkflowState, kind: ProcessEventKind, content: &str) {
    use robson_core::entities::process_event::Model as ProcessEvent;
    if let (Some(agent_db), Some(conv_id)) = (&state.agent_db, state.conversation_id) {
        if let Err(e) = ProcessEvent::insert(agent_db, conv_id, kind.as_str(), content).await {
            warn!(error = %e, "orchestrator: failed to emit process event");
        }
    }
}

pub async fn run_pipeline(
    state: Arc<WorkflowState>,
    shutdown: watch::Receiver<bool>,
    keep_tmp: bool,
    dev: bool,
) -> Result<()> {
    let tasks = Task::find_pending(&state.workflow_db).await?;

    if tasks.is_empty() {
        info!("no pending tasks found");
        return Ok(());
    }

    info!(
        task_count = tasks.len(),
        concurrency = state.config.orchestrator.concurrency,
        "pipeline started"
    );

    let semaphore = Arc::new(Semaphore::new(state.config.orchestrator.concurrency));
    let mut handles = Vec::new();

    for task in tasks {
        if *shutdown.borrow() {
            warn!("shutdown requested — stopping task dispatch");
            break;
        }

        let permit = semaphore.clone().acquire_owned().await?;
        let state = state.clone();
        let mut task_shutdown = shutdown.clone();
        let key = task.jira_key.clone();

        let handle = tokio::spawn(async move {
            let _permit = permit;

            tokio::select! {
                result = process_task(state.clone(), &task, keep_tmp, dev) => {
                    match result {
                        Ok(_) => info!(jira_key = %key, "task done"),
                        Err(e) => {
                            error!(jira_key = %key, error = %e, "task failed");
                            let _ = Task::set_failed(&state.workflow_db, &key, &format!("{:#}", e)).await;
                        }
                    }
                }
                _ = task_shutdown.changed() => {
                    warn!(jira_key = %key, "task aborted by shutdown signal");
                    let _ = Task::set_failed(&state.workflow_db, &key, "aborted by shutdown").await;
                }
            }
        });

        handles.push(handle);
    }

    for handle in handles {
        let _ = handle.await;
    }

    info!("pipeline complete");
    Ok(())
}

async fn process_task(
    state: Arc<WorkflowState>,
    task: &Task,
    keep_tmp: bool,
    dev: bool,
) -> Result<()> {
    Task::set_in_progress(&state.workflow_db, &task.jira_key).await?;
    info!(jira_key = %task.jira_key, "task set to in_progress");

    let (ctx, repo, temp_dir) = setup::phase1_setup(&state, task).await?;

    debug!(
        jira_key = %task.jira_key,
        summary = %truncate_log(&ctx.summary, 120),
        repo_slug = %repo.slug,
        "task processing started"
    );

    let (temp_dir, _branch_name, validation_passed) = implementation::run_implementation(
        &state.config,
        &state.workflow_db,
        task,
        &repo,
        &ctx,
        temp_dir,
        false,
        dev,
    )
    .await?;

    if !validation_passed {
        warn!(jira_key = %task.jira_key, "validation failed — skipping PR, marking as unfinished");
        let jira_url = format!(
            "{}/browse/{}",
            state.config.jira.base_url.trim_end_matches('/'),
            task.jira_key
        );
        let _ =
            notification::notify_validation_failure(&state.config, task, &ctx, &jira_url, &state)
                .await;
        Task::set_unfinished(&state.workflow_db, &task.jira_key, "Validation failed: tests found outside tests/ directory or Docker test validation failed").await?;
        return Ok(());
    }

    notification::run_notification(&state.config, &state.workflow_db, task, &ctx, false, &state)
        .await?;

    if keep_tmp {
        let path = temp_dir.keep();
        debug!(jira_key = %task.jira_key, path = %path.display(), "keep_tmp: working directory preserved");
    }

    info!(jira_key = %task.jira_key, "task processing complete");
    Ok(())
}
