use axum::{
    extract::{Path, State},
    http::StatusCode,
    response::Json,
};
use ontoref_derive::onto_api;
use provisioning_orchestrator::{
    config::OrchestratorConfig,
    preconditions::{ComponentRef, WorkspacePath},
    ClusterWorkflow, ComponentWorkflow, CreateServerWorkflow, SharedState, TaskStatus,
    TaskservWorkflow, WorkflowTask,
};
use tracing::{error, info, warn};
use uuid::Uuid;

use super::ApiResponse;

#[onto_api(
    method = "POST",
    path = "/workflows/servers/create",
    description = "Enqueue server creation workflow",
    auth = "bearer",
    actors = "developer, agent",
    tags = "workflows, servers",
    feature = ""
)]
pub async fn create_server_workflow(
    State(state): State<SharedState>,
    Json(workflow): Json<CreateServerWorkflow>,
) -> Result<Json<ApiResponse<String>>, StatusCode> {
    let task_id = Uuid::new_v4().to_string();

    // PROPER ARCHITECTURE: CLI renders script, orchestrator executes it
    // If script_compressed is provided: execute it (that's ALL the orchestrator
    // does) If NOT provided: error (legacy mode should not happen)
    let task = if let Some(ref script_compressed) = workflow.script_compressed {
        // CLI has provided the COMPLETE SCRIPT ready to execute
        // No command construction, no decision logic
        // Just: decompress -> execute

        // Store script in temp file for execution — async write to avoid blocking tokio worker
        // threads when multiple concurrent submissions arrive simultaneously.
        let script_file = format!("/tmp/orchestrator_script_{}.tar.gz.b64", task_id);
        tokio::fs::write(&script_file, script_compressed)
            .await
            .map_err(|e| {
                error!("Failed to write script payload to {}: {}", script_file, e);
                StatusCode::INTERNAL_SERVER_ERROR
            })?;

        WorkflowTask {
            id: task_id.clone(),
            name: if workflow.servers.is_empty() {
                "execute_servers_script_all".to_string()
            } else {
                format!("execute_servers_script_{}", workflow.servers.join("_"))
            },
            // Execute the decompressed script directly
            command: "bash".to_string(),
            args: vec![
                "-c".to_string(),
                // Decompress: base64 decode -> gunzip -> extract script.sh -> execute
                // CRITICAL: Use '+x' to DISABLE debug mode and prevent credential exposure
                // Even if script contains 'set -x', it won't execute with +x flag
                format!(
                    "base64 -d < {} | gunzip | tar -xOf - script.sh | bash +x",
                    script_file
                ),
            ],
            dependencies: vec![],
            status: TaskStatus::Pending,
            created_at: chrono::Utc::now(),
            started_at: None,
            completed_at: None,
            output: None,
            error: None,
            tags: Default::default(),
        }
    } else {
        // LEGACY: Construct command from parameters (deprecated)
        let mut args = vec![
            "--infra".to_string(),
            workflow.infra.clone(),
            "--settings".to_string(),
            workflow.settings.clone(),
        ];

        for server in &workflow.servers {
            args.push(server.clone());
        }

        if workflow.check_mode {
            args.push("--check".to_string());
        }
        if workflow.wait {
            args.push("--wait".to_string());
        }

        WorkflowTask {
            id: task_id.clone(),
            name: if workflow.servers.is_empty() {
                "create_servers_all".to_string()
            } else {
                format!("create_servers_{}", workflow.servers.join("_"))
            },
            command: format!("{} servers create", state.args.provisioning_path),
            args,
            dependencies: vec![],
            status: TaskStatus::Pending,
            created_at: chrono::Utc::now(),
            started_at: None,
            completed_at: None,
            output: None,
            error: None,
            tags: Default::default(),
        }
    };

    let server_summary = if workflow.servers.is_empty() {
        "all servers".to_string()
    } else {
        format!("{} server(s)", workflow.servers.len())
    };

    match state.task_storage.enqueue(task, 5).await {
        Ok(()) => {
            info!(
                "Enqueued server creation workflow ({}): {} | infra: {}",
                server_summary, task_id, workflow.infra
            );
            Ok(Json(ApiResponse::success(task_id)))
        }
        Err(e) => {
            error!(
                "Failed to enqueue server creation task ({}): {}",
                server_summary, e
            );
            Err(StatusCode::INTERNAL_SERVER_ERROR)
        }
    }
}

#[onto_api(
    method = "POST",
    path = "/workflows/taskserv/create",
    description = "Enqueue taskserv operation workflow",
    auth = "bearer",
    actors = "developer, agent",
    tags = "workflows, taskservs",
    feature = ""
)]
pub async fn create_taskserv_workflow(
    State(state): State<SharedState>,
    Json(workflow): Json<TaskservWorkflow>,
) -> Result<Json<ApiResponse<String>>, StatusCode> {
    let task_id = Uuid::new_v4().to_string();

    let task = WorkflowTask {
        id: task_id.clone(),
        name: format!("{}_taskserv", workflow.operation),
        command: format!(
            "{} taskserv {}",
            state.args.provisioning_path, workflow.operation
        ),
        args: vec![
            workflow.taskserv.clone(),
            "--infra".to_string(),
            workflow.infra.clone(),
            "--settings".to_string(),
            workflow.settings.clone(),
            if workflow.check_mode {
                "--check".to_string()
            } else {
                "".to_string()
            },
            if workflow.wait {
                "--wait".to_string()
            } else {
                "".to_string()
            },
        ]
        .into_iter()
        .filter(|s| !s.is_empty())
        .collect(),
        dependencies: vec![],
        status: TaskStatus::Pending,
        created_at: chrono::Utc::now(),
        started_at: None,
        completed_at: None,
        output: None,
        error: None,
        tags: Default::default(),
    };

    match state.task_storage.enqueue(task, 6).await {
        Ok(()) => {
            info!(
                "Enqueued taskserv {} workflow: {}",
                workflow.operation, task_id
            );
            Ok(Json(ApiResponse::success(task_id)))
        }
        Err(e) => {
            error!("Failed to enqueue task: {}", e);
            Err(StatusCode::INTERNAL_SERVER_ERROR)
        }
    }
}

#[onto_api(
    method = "POST",
    path = "/api/v1/workflows/component/{operation}",
    description = "Unified component lifecycle endpoint — polymorphic dispatch by deploy mode (taskserv/cluster/container)",
    auth = "bearer",
    actors = "developer, agent, mcp",
    tags = "workflows, components",
    feature = ""
)]
pub async fn create_component_workflow(
    State(state): State<SharedState>,
    Path(operation): Path<String>,
    Json(workflow): Json<ComponentWorkflow>,
) -> Result<Json<ApiResponse<String>>, StatusCode> {
    // Feature flag — return 404 when disabled so legacy routing is unaffected.
    let cfg = OrchestratorConfig::load_with_cli_args(&state.args).unwrap_or_default();
    if !cfg.orchestrator.features.enable_component_endpoint {
        warn!(
            "component endpoint disabled by feature flag; op={operation} component={}",
            workflow.component
        );
        return Err(StatusCode::NOT_FOUND);
    }

    // Validate operation is in the allowed set.
    const WRITE_OPS: &[&str] = &[
        "install",
        "delete",
        "update",
        "reinstall",
        "restart",
        "backup",
        "restore",
        "check-updates",
    ];
    const READ_OPS: &[&str] = &["status", "health", "list", "show"];

    if !WRITE_OPS.contains(&operation.as_str()) && !READ_OPS.contains(&operation.as_str()) {
        warn!("unknown component operation '{operation}'");
        return Err(StatusCode::BAD_REQUEST);
    }

    // Precondition gate — skipped for read-only ops inside verify_component_preconditions.
    let ws_path = WorkspacePath {
        root: &workflow.workspace,
        infra: &workflow.infra,
        provisioning: &workflow.provisioning,
    };
    let comp_mode = workflow.mode.as_deref().unwrap_or("cluster");
    let comp_ref = ComponentRef {
        name: &workflow.component,
        mode: comp_mode,
        target: &workflow.server,
        namespace: workflow.namespace.as_deref(),
        ssh_user: &workflow.ssh_user,
        ssh_key_path: workflow.ssh_key_path.as_deref(),
    };

    if let Err(pre_err) =
        provisioning_orchestrator::preconditions::verify_component_preconditions(
            &ws_path, &comp_ref, &operation,
        )
        .await
    {
        warn!(
            "precondition gate rejected component='{}' op='{operation}': {pre_err}",
            workflow.component
        );
        return Ok(Json(ApiResponse::error(pre_err.to_string())));
    }

    let task_id = Uuid::new_v4().to_string();

    // CLI sends a pre-built bundle (base64 tar.gz). Orchestrator only does SCP + SSH.
    // No Nu, no provisioning CLI invocation.
    let Some(ref bundle_b64) = workflow.script_compressed else {
        warn!(
            "component {} op={operation}: missing script_compressed bundle — rejecting",
            workflow.component
        );
        return Ok(Json(ApiResponse::error(
            "component workflow requires script_compressed bundle from CLI".to_string(),
        )));
    };

    let bundle_file = format!("/tmp/orchestrator_comp_{}.tar.gz.b64", task_id);
    tokio::fs::write(&bundle_file, bundle_b64)
        .await
        .map_err(|e| {
            error!("Failed to write component bundle {}: {}", bundle_file, e);
            StatusCode::INTERNAL_SERVER_ERROR
        })?;

    // Orchestrator is an automation daemon — no persistent known_hosts trust store.
    // UserKnownHostsFile=/dev/null prevents stale-key failures after server rebuilds.
    let key_opt = workflow
        .ssh_key_path
        .as_deref()
        .map(|k| format!("-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i '{k}'"))
        .unwrap_or_else(|| "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null".to_string());
    let name = &workflow.component;
    let user = &workflow.ssh_user;
    let server = &workflow.server;
    let check_env = if workflow.check_mode {
        "CHECK_MODE=1 "
    } else {
        ""
    };
    // debug=true: bash -x traces every command; remote /tmp/{name} is left intact for ssh inspection.
    let bash_runner = if workflow.debug { "bash -x" } else { "bash" };
    let remote_cleanup = if workflow.debug {
        // Keep tar.gz and extracted dir on remote so the user can ssh in and inspect.
        format!("echo '[debug] leaving /tmp/{name} and /tmp/{name}.tar.gz on remote for inspection'")
    } else {
        format!("rm -f /tmp/{name}.tar.gz && rm -rf /tmp/{name}")
    };

    // Decode bundle locally, SCP to remote /tmp, extract and execute install script.
    // Pass the operation (install/delete/update/reinstall/…) as $1 to the install script.
    let ssh_cmd = format!(
        "base64 -d < '{bundle_file}' > /tmp/{name}.tar.gz \
         && scp {key_opt} /tmp/{name}.tar.gz '{user}@{server}:/tmp/' \
         && ssh {key_opt} '{user}@{server}' \
             'rm -rf /tmp/{name} && mkdir -p /tmp/{name} \
              && tar xzf /tmp/{name}.tar.gz -C /tmp/{name} \
              && cd /tmp/{name} \
              && {check_env}sudo {bash_runner} install-{name}.sh {operation} \
              ; rc=$?; {remote_cleanup}; exit $rc' \
         ; rc=$?; rm -f '{bundle_file}' /tmp/{name}.tar.gz; exit $rc",
    );

    let mut component_tags = std::collections::HashMap::new();
    component_tags.insert("type".to_string(), "component".to_string());
    component_tags.insert("component".to_string(), workflow.component.clone());
    component_tags.insert("server".to_string(), workflow.server.clone());
    component_tags.insert("workspace".to_string(), workflow.workspace.clone());
    component_tags.insert("operation".to_string(), operation.clone());

    let task = WorkflowTask {
        id: task_id.clone(),
        name: format!("component_{operation}_{}", workflow.component),
        command: "bash".to_string(),
        args: vec!["-c".to_string(), ssh_cmd],
        dependencies: vec![],
        status: TaskStatus::Pending,
        created_at: chrono::Utc::now(),
        started_at: None,
        completed_at: None,
        output: None,
        error: None,
        tags: component_tags,
    };

    match state.task_storage.enqueue(task, 6).await {
        Ok(()) => {
            info!(
                "enqueued component {} op={operation} task_id={task_id}",
                workflow.component
            );
            Ok(Json(ApiResponse::success(task_id)))
        }
        Err(e) => {
            error!("failed to enqueue component task: {e}");
            Err(StatusCode::INTERNAL_SERVER_ERROR)
        }
    }
}

#[onto_api(
    method = "POST",
    path = "/workflows/cluster/create",
    description = "Enqueue cluster creation workflow",
    auth = "bearer",
    actors = "developer, agent",
    tags = "workflows, clusters",
    feature = ""
)]
pub async fn create_cluster_workflow(
    State(state): State<SharedState>,
    Json(workflow): Json<ClusterWorkflow>,
) -> Result<Json<ApiResponse<String>>, StatusCode> {
    let task_id = Uuid::new_v4().to_string();

    let task = WorkflowTask {
        id: task_id.clone(),
        name: format!("{}_cluster", workflow.operation),
        command: format!(
            "{} cluster {}",
            state.args.provisioning_path, workflow.operation
        ),
        args: vec![
            workflow.cluster_type.clone(),
            "--infra".to_string(),
            workflow.infra.clone(),
            "--settings".to_string(),
            workflow.settings.clone(),
            if workflow.check_mode {
                "--check".to_string()
            } else {
                "".to_string()
            },
            if workflow.wait {
                "--wait".to_string()
            } else {
                "".to_string()
            },
        ]
        .into_iter()
        .filter(|s| !s.is_empty())
        .collect(),
        dependencies: vec![],
        status: TaskStatus::Pending,
        created_at: chrono::Utc::now(),
        started_at: None,
        completed_at: None,
        output: None,
        error: None,
        tags: Default::default(),
    };

    match state.task_storage.enqueue(task, 7).await {
        Ok(()) => {
            info!(
                "Enqueued cluster {} workflow: {}",
                workflow.operation, task_id
            );
            Ok(Json(ApiResponse::success(task_id)))
        }
        Err(e) => {
            error!("Failed to enqueue task: {}", e);
            Err(StatusCode::INTERNAL_SERVER_ERROR)
        }
    }
}
