use std::sync::Arc;

use anyhow::Result;
use sea_orm::DatabaseConnection;
use tracing::{debug, error, info, warn};

use crate::config::WorkflowState;
use crate::entities::repo::Model as Repo;
use crate::entities::task::validate_repo_url;
use crate::entities::task::Model as Task;
use crate::entities::task_execution::{EngineType, Model as TaskExecution, TaskStatus};

use robson_core::plugin::ProcessEventKind;

use super::{
    emit_process_event, implementation::run_implementation, notification::run_notification,
    setup::phase1_setup,
};

pub struct RunTaskFlags {
    pub force: bool,
    pub no_notify: bool,
    pub no_push: bool,
    pub keep_tmp: bool,
    pub dev: bool,
    /// When set, pending `task_executions` for this task are upserted to use this registered repo
    /// (`repos.slug`) before the run starts.
    pub repo_url: Option<String>,
}

pub fn guard_rerun(ex: &TaskExecution, jira_key: &str, force: bool) -> Result<()> {
    if (ex.status == TaskStatus::Done || ex.status == TaskStatus::InProgress) && !force {
        anyhow::bail!(
            "Task {} is already '{}'. Use --force to re-run it.",
            jira_key,
            ex.status
        );
    }
    Ok(())
}

/// If `repo_url` is present, resolve it to a registered repo and upsert the pending
/// `task_executions` row so `repo_id` matches (creating a row when none exists yet).
async fn upsert_pending_execution_repo(
    db: &DatabaseConnection,
    task: &Task,
    jira_key: &str,
    repo_url: Option<&str>,
) -> Result<()> {
    let Some(url) = repo_url.map(str::trim).filter(|s| !s.is_empty()) else {
        return Ok(());
    };
    validate_repo_url(url)?;
    let repo = Repo::find_by_url(db, url)
        .await?
        .ok_or_else(|| anyhow::anyhow!("No registered repo matches repo_url '{}'", url))?;

    let latest = TaskExecution::find_latest_for_task(db, task.id).await?;
    let (summary, description, assignee, engine_type) = match &latest {
        Some(ex) => (
            ex.summary.clone(),
            ex.description.clone(),
            ex.assignee.clone(),
            ex.engine_type.clone(),
        ),
        None => (jira_key.to_string(), None, None, EngineType::ClaudeCode),
    };

    TaskExecution::upsert_pending_work(
        db,
        task.id,
        repo.id,
        &summary,
        description.as_deref(),
        assignee.as_deref(),
        engine_type,
    )
    .await?;
    Ok(())
}

pub async fn run_single_task(
    state: Arc<WorkflowState>,
    jira_key: &str,
    flags: RunTaskFlags,
) -> Result<()> {
    info!(jira_key = %jira_key, "single-task run starting");

    let task = Task::find_by_key(&state.workflow_db, jira_key)
        .await?
        .ok_or_else(|| {
            anyhow::anyhow!(
                "Task '{}' not found in DB. Create it first with `robson task create`.",
                jira_key
            )
        })?;

    let repo_url = flags.repo_url.as_deref();

    if TaskExecution::find_latest_for_task(&state.workflow_db, task.id)
        .await?
        .is_none()
    {
        if repo_url.is_none_or(|u| u.trim().is_empty()) {
            anyhow::bail!(
                "Task '{}' has no task_executions row. Create the task (with a repo) first, or pass repo_url= on /run-task.",
                jira_key
            );
        }
        upsert_pending_execution_repo(&state.workflow_db, &task, jira_key, repo_url).await?;
    }

    let ex = TaskExecution::find_latest_for_task(&state.workflow_db, task.id)
        .await?
        .ok_or_else(|| {
            anyhow::anyhow!(
                "Task '{}' has no task_executions row after repo upsert.",
                jira_key
            )
        })?;

    debug!(
        jira_key = %jira_key,
        status = %ex.status,
        force = flags.force,
        no_push = flags.no_push,
        no_notify = flags.no_notify,
        "task found"
    );

    guard_rerun(&ex, jira_key, flags.force)?;
    if flags.force && (ex.status == TaskStatus::Done || ex.status == TaskStatus::InProgress) {
        info!(jira_key = %jira_key, "force flag set — resetting status to pending");
        Task::reset_to_pending(&state.workflow_db, jira_key).await?;
    }
    upsert_pending_execution_repo(&state.workflow_db, &task, jira_key, repo_url).await?;
    Task::set_in_progress(&state.workflow_db, jira_key).await?;
    info!(jira_key = %jira_key, "task set to in_progress");

    let (ctx, repo, temp_dir) = match phase1_setup(&state, &task).await {
        Ok(v) => v,
        Err(e) => {
            error!(jira_key = %jira_key, error = %e, "phase1 setup failed");
            emit_process_event(
                &state,
                ProcessEventKind::Failed,
                &format!(":x: Task *{}* setup failed: {:#}", jira_key, e),
            )
            .await;
            Task::set_failed(&state.workflow_db, jira_key, &format!("{:#}", e)).await?;
            return Err(e);
        }
    };
    debug!(jira_key = %jira_key, repo_slug = %repo.slug, "phase1 setup complete");

    match run_implementation(
        &state.config,
        &state.workflow_db,
        &task,
        &repo,
        &ctx,
        temp_dir,
        flags.no_push,
        flags.dev,
    )
    .await
    {
        Err(e) => {
            error!(jira_key = %jira_key, error = %e, "implementation failed");
            emit_process_event(
                &state,
                ProcessEventKind::Failed,
                &format!(":x: Task *{}* implementation failed: {:#}", jira_key, e),
            )
            .await;
            Task::set_failed(&state.workflow_db, jira_key, &format!("{:#}", e)).await?;
            return Err(e);
        }
        Ok((temp_dir, _branch_name, validation_passed)) => {
            if !validation_passed {
                warn!(jira_key = %jira_key, "validation failed — skipping PR");
                emit_process_event(
                    &state,
                    ProcessEventKind::Failed,
                    &format!(":x: Task *{}* validation failed — no PR created.", jira_key),
                )
                .await;
                Task::set_unfinished(&state.workflow_db, jira_key, "Validation failed: tests found outside tests/ directory or Docker test validation failed").await?;
                return Ok(());
            }

            emit_process_event(
                &state,
                ProcessEventKind::Progress,
                &format!(
                    ":white_check_mark: Task *{}* implementation complete, opening PR…",
                    jira_key
                ),
            )
            .await;

            debug!(jira_key = %jira_key, no_notify = flags.no_notify, "running notification phase");
            let result = run_notification(
                &state.config,
                &state.workflow_db,
                &task,
                &ctx,
                flags.no_notify,
                &state,
            )
            .await;

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

            if let Err(e) = result {
                error!(jira_key = %jira_key, error = %e, "notification failed");
                Task::set_failed(&state.workflow_db, jira_key, &format!("{:#}", e)).await?;
                return Err(e);
            }
        }
    }

    info!(jira_key = %jira_key, "single-task run complete");
    Ok(())
}
