//! Merge operation handlers for orchestrator tasks.
//!
//! These endpoints allow orchestrators to merge subtask branches.
//! Commands are forwarded to the daemon via WebSocket; the daemon
//! responds asynchronously through the WebSocket channel.

use axum::{
    extract::{Path, State},
    http::StatusCode,
    response::IntoResponse,
    Json,
};
use uuid::Uuid;

use crate::db::models::{
    BranchListResponse, MergeCommitRequest, MergeCompleteCheckResponse, MergeResolveRequest,
    MergeResultResponse, MergeSkipRequest, MergeStartRequest, MergeStatusResponse,
};
use crate::db::repository;
use crate::server::messages::ApiError;
use crate::server::state::{DaemonCommand, SharedState};

/// Get the daemon ID for a task, returning error if not found.
async fn get_task_daemon_id(
    state: &SharedState,
    task_id: Uuid,
) -> Result<Uuid, (StatusCode, Json<ApiError>)> {
    let pool = state.db_pool.as_ref().ok_or_else(|| {
        (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("service_unavailable", "Database not configured")),
        )
    })?;

    // Get task and its daemon_id
    let task = repository::get_task(pool, task_id)
        .await
        .map_err(|e| {
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("database_error", format!("Database error: {}", e))),
            )
        })?
        .ok_or_else(|| {
            (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("not_found", format!("Task {} not found", task_id))),
            )
        })?;

    task.daemon_id.ok_or_else(|| {
        (
            StatusCode::BAD_REQUEST,
            Json(ApiError::new("bad_request", "Task has no assigned daemon")),
        )
    })
}

/// List all subtask branches for a task.
///
/// GET /api/v1/mesh/tasks/{id}/branches
#[utoipa::path(
    get,
    path = "/api/v1/mesh/tasks/{id}/branches",
    params(
        ("id" = Uuid, Path, description = "Task ID")
    ),
    responses(
        (status = 202, description = "Command sent to daemon"),
        (status = 404, description = "Task not found"),
        (status = 503, description = "Database not configured")
    ),
    tag = "Mesh"
)]
pub async fn list_branches(
    State(state): State<SharedState>,
    Path(task_id): Path<Uuid>,
) -> impl IntoResponse {
    let daemon_id = match get_task_daemon_id(&state, task_id).await {
        Ok(id) => id,
        Err(e) => return e.into_response(),
    };

    let command = DaemonCommand::ListBranches { task_id };

    match state.send_daemon_command(daemon_id, command).await {
        Ok(()) => (
            StatusCode::ACCEPTED,
            Json(BranchListResponse { branches: vec![] }),
        )
            .into_response(),
        Err(e) => (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("daemon_error", e)),
        )
            .into_response(),
    }
}

/// Start merging a subtask branch.
///
/// POST /api/v1/mesh/tasks/{id}/merge/start
#[utoipa::path(
    post,
    path = "/api/v1/mesh/tasks/{id}/merge/start",
    params(
        ("id" = Uuid, Path, description = "Task ID")
    ),
    request_body = MergeStartRequest,
    responses(
        (status = 202, description = "Merge command sent"),
        (status = 404, description = "Task not found"),
        (status = 503, description = "Database not configured or daemon not connected")
    ),
    tag = "Mesh"
)]
pub async fn merge_start(
    State(state): State<SharedState>,
    Path(task_id): Path<Uuid>,
    Json(req): Json<MergeStartRequest>,
) -> impl IntoResponse {
    let daemon_id = match get_task_daemon_id(&state, task_id).await {
        Ok(id) => id,
        Err(e) => return e.into_response(),
    };

    let command = DaemonCommand::MergeStart {
        task_id,
        source_branch: req.source_branch,
    };

    match state.send_daemon_command(daemon_id, command).await {
        Ok(()) => (
            StatusCode::ACCEPTED,
            Json(MergeResultResponse {
                success: true,
                message: "Merge command sent".to_string(),
                commit_sha: None,
                conflicts: None,
            }),
        )
            .into_response(),
        Err(e) => (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("daemon_error", e)),
        )
            .into_response(),
    }
}

/// Get current merge status.
///
/// GET /api/v1/mesh/tasks/{id}/merge/status
#[utoipa::path(
    get,
    path = "/api/v1/mesh/tasks/{id}/merge/status",
    params(
        ("id" = Uuid, Path, description = "Task ID")
    ),
    responses(
        (status = 202, description = "Status request sent"),
        (status = 404, description = "Task not found"),
        (status = 503, description = "Database not configured or daemon not connected")
    ),
    tag = "Mesh"
)]
pub async fn merge_status(
    State(state): State<SharedState>,
    Path(task_id): Path<Uuid>,
) -> impl IntoResponse {
    let daemon_id = match get_task_daemon_id(&state, task_id).await {
        Ok(id) => id,
        Err(e) => return e.into_response(),
    };

    let command = DaemonCommand::MergeStatus { task_id };

    match state.send_daemon_command(daemon_id, command).await {
        Ok(()) => (
            StatusCode::ACCEPTED,
            Json(MergeStatusResponse {
                in_progress: false,
                source_branch: None,
                conflicted_files: vec![],
            }),
        )
            .into_response(),
        Err(e) => (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("daemon_error", e)),
        )
            .into_response(),
    }
}

/// Resolve a merge conflict.
///
/// POST /api/v1/mesh/tasks/{id}/merge/resolve
#[utoipa::path(
    post,
    path = "/api/v1/mesh/tasks/{id}/merge/resolve",
    params(
        ("id" = Uuid, Path, description = "Task ID")
    ),
    request_body = MergeResolveRequest,
    responses(
        (status = 202, description = "Resolve command sent"),
        (status = 404, description = "Task not found"),
        (status = 503, description = "Database not configured or daemon not connected")
    ),
    tag = "Mesh"
)]
pub async fn merge_resolve(
    State(state): State<SharedState>,
    Path(task_id): Path<Uuid>,
    Json(req): Json<MergeResolveRequest>,
) -> impl IntoResponse {
    let daemon_id = match get_task_daemon_id(&state, task_id).await {
        Ok(id) => id,
        Err(e) => return e.into_response(),
    };

    let command = DaemonCommand::MergeResolve {
        task_id,
        file: req.file,
        strategy: req.strategy,
    };

    match state.send_daemon_command(daemon_id, command).await {
        Ok(()) => (
            StatusCode::ACCEPTED,
            Json(MergeResultResponse {
                success: true,
                message: "Resolve command sent".to_string(),
                commit_sha: None,
                conflicts: None,
            }),
        )
            .into_response(),
        Err(e) => (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("daemon_error", e)),
        )
            .into_response(),
    }
}

/// Commit the current merge.
///
/// POST /api/v1/mesh/tasks/{id}/merge/commit
#[utoipa::path(
    post,
    path = "/api/v1/mesh/tasks/{id}/merge/commit",
    params(
        ("id" = Uuid, Path, description = "Task ID")
    ),
    request_body = MergeCommitRequest,
    responses(
        (status = 202, description = "Commit command sent"),
        (status = 404, description = "Task not found"),
        (status = 503, description = "Database not configured or daemon not connected")
    ),
    tag = "Mesh"
)]
pub async fn merge_commit(
    State(state): State<SharedState>,
    Path(task_id): Path<Uuid>,
    Json(req): Json<MergeCommitRequest>,
) -> impl IntoResponse {
    let daemon_id = match get_task_daemon_id(&state, task_id).await {
        Ok(id) => id,
        Err(e) => return e.into_response(),
    };

    let command = DaemonCommand::MergeCommit {
        task_id,
        message: req.message,
    };

    match state.send_daemon_command(daemon_id, command).await {
        Ok(()) => (
            StatusCode::ACCEPTED,
            Json(MergeResultResponse {
                success: true,
                message: "Commit command sent".to_string(),
                commit_sha: None,
                conflicts: None,
            }),
        )
            .into_response(),
        Err(e) => (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("daemon_error", e)),
        )
            .into_response(),
    }
}

/// Abort the current merge.
///
/// POST /api/v1/mesh/tasks/{id}/merge/abort
#[utoipa::path(
    post,
    path = "/api/v1/mesh/tasks/{id}/merge/abort",
    params(
        ("id" = Uuid, Path, description = "Task ID")
    ),
    responses(
        (status = 202, description = "Abort command sent"),
        (status = 404, description = "Task not found"),
        (status = 503, description = "Database not configured or daemon not connected")
    ),
    tag = "Mesh"
)]
pub async fn merge_abort(
    State(state): State<SharedState>,
    Path(task_id): Path<Uuid>,
) -> impl IntoResponse {
    let daemon_id = match get_task_daemon_id(&state, task_id).await {
        Ok(id) => id,
        Err(e) => return e.into_response(),
    };

    let command = DaemonCommand::MergeAbort { task_id };

    match state.send_daemon_command(daemon_id, command).await {
        Ok(()) => (
            StatusCode::ACCEPTED,
            Json(MergeResultResponse {
                success: true,
                message: "Abort command sent".to_string(),
                commit_sha: None,
                conflicts: None,
            }),
        )
            .into_response(),
        Err(e) => (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("daemon_error", e)),
        )
            .into_response(),
    }
}

/// Skip merging a subtask branch.
///
/// POST /api/v1/mesh/tasks/{id}/merge/skip
#[utoipa::path(
    post,
    path = "/api/v1/mesh/tasks/{id}/merge/skip",
    params(
        ("id" = Uuid, Path, description = "Task ID")
    ),
    request_body = MergeSkipRequest,
    responses(
        (status = 202, description = "Skip command sent"),
        (status = 404, description = "Task not found"),
        (status = 503, description = "Database not configured or daemon not connected")
    ),
    tag = "Mesh"
)]
pub async fn merge_skip(
    State(state): State<SharedState>,
    Path(task_id): Path<Uuid>,
    Json(req): Json<MergeSkipRequest>,
) -> impl IntoResponse {
    let daemon_id = match get_task_daemon_id(&state, task_id).await {
        Ok(id) => id,
        Err(e) => return e.into_response(),
    };

    let command = DaemonCommand::MergeSkip {
        task_id,
        subtask_id: req.subtask_id,
        reason: req.reason,
    };

    match state.send_daemon_command(daemon_id, command).await {
        Ok(()) => (
            StatusCode::ACCEPTED,
            Json(MergeResultResponse {
                success: true,
                message: "Skip command sent".to_string(),
                commit_sha: None,
                conflicts: None,
            }),
        )
            .into_response(),
        Err(e) => (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("daemon_error", e)),
        )
            .into_response(),
    }
}

/// Check if all branches are merged or skipped.
///
/// GET /api/v1/mesh/tasks/{id}/merge/check
#[utoipa::path(
    get,
    path = "/api/v1/mesh/tasks/{id}/merge/check",
    params(
        ("id" = Uuid, Path, description = "Task ID")
    ),
    responses(
        (status = 202, description = "Check command sent"),
        (status = 404, description = "Task not found"),
        (status = 503, description = "Database not configured or daemon not connected")
    ),
    tag = "Mesh"
)]
pub async fn merge_check(
    State(state): State<SharedState>,
    Path(task_id): Path<Uuid>,
) -> impl IntoResponse {
    let daemon_id = match get_task_daemon_id(&state, task_id).await {
        Ok(id) => id,
        Err(e) => return e.into_response(),
    };

    let command = DaemonCommand::CheckMergeComplete { task_id };

    match state.send_daemon_command(daemon_id, command).await {
        Ok(()) => (
            StatusCode::ACCEPTED,
            Json(MergeCompleteCheckResponse {
                can_complete: true,
                unmerged_branches: vec![],
                merged_count: 0,
                skipped_count: 0,
            }),
        )
            .into_response(),
        Err(e) => (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("daemon_error", e)),
        )
            .into_response(),
    }
}
