//! HTTP handlers for chain CRUD operations.
//!
//! Chains are DAGs (directed acyclic graphs) of contracts for multi-contract orchestration.

use axum::{
    extract::{Path, Query, State},
    http::StatusCode,
    response::IntoResponse,
    Json,
};
use serde::Deserialize;
use utoipa::ToSchema;
use uuid::Uuid;

use crate::db::models::{
    AddChainRepositoryRequest, AddContractDefinitionRequest, ChainContractDefinition,
    ChainContractDetail, ChainDefinitionGraphResponse, ChainEditorData, ChainEvent,
    ChainGraphResponse, ChainRepository, ChainSummary, ChainWithContracts, CreateChainRequest,
    InitChainRequest, InitChainResponse, StartChainRequest, StartChainResponse, UpdateChainRequest,
    UpdateContractDefinitionRequest,
};
use crate::db::repository::{self, RepositoryError};
use crate::server::auth::Authenticated;
use crate::server::messages::ApiError;
use crate::server::state::SharedState;

// =============================================================================
// Query Parameters
// =============================================================================

/// Query parameters for listing chains.
#[derive(Debug, Deserialize, ToSchema)]
pub struct ListChainsQuery {
    /// Filter by status (active, completed, archived)
    pub status: Option<String>,
    /// Maximum number of results
    #[serde(default = "default_limit")]
    pub limit: i32,
    /// Offset for pagination
    #[serde(default)]
    pub offset: i32,
}

fn default_limit() -> i32 {
    50
}

// =============================================================================
// Response Types
// =============================================================================

/// Response for listing chains.
#[derive(Debug, serde::Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ChainListResponse {
    pub chains: Vec<ChainSummary>,
    pub total: i64,
}

// =============================================================================
// Handlers
// =============================================================================

/// List chains for the authenticated user.
///
/// GET /api/v1/chains
#[utoipa::path(
    get,
    path = "/api/v1/chains",
    responses(
        (status = 200, description = "List of chains", body = ChainListResponse),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn list_chains(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Query(query): Query<ListChainsQuery>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    match repository::list_chains_for_owner(pool, auth.owner_id).await {
        Ok(mut chains) => {
            // Apply filters
            if let Some(status) = &query.status {
                chains.retain(|c| c.status == *status);
            }
            // Apply pagination
            let total = chains.len() as i64;
            let chains: Vec<_> = chains
                .into_iter()
                .skip(query.offset as usize)
                .take(query.limit as usize)
                .collect();
            Json(ChainListResponse { chains, total }).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to list chains: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Create a new chain with contracts.
///
/// POST /api/v1/chains
#[utoipa::path(
    post,
    path = "/api/v1/chains",
    request_body = CreateChainRequest,
    responses(
        (status = 201, description = "Chain created"),
        (status = 400, description = "Invalid request", body = ApiError),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn create_chain(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Json(req): Json<CreateChainRequest>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Validate the request
    if req.name.trim().is_empty() {
        return (
            StatusCode::BAD_REQUEST,
            Json(ApiError::new("VALIDATION_ERROR", "Chain name cannot be empty")),
        )
            .into_response();
    }

    match repository::create_chain_for_owner(pool, auth.owner_id, req).await {
        Ok(chain) => (StatusCode::CREATED, Json(chain)).into_response(),
        Err(e) => {
            tracing::error!("Failed to create chain: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Initialize a directive-driven chain.
///
/// Creates a directive contract that will research, plan, create, and orchestrate
/// a chain of contracts to accomplish the given goal. The directive contract goes
/// through Research -> Specify -> Plan -> Execute -> Review phases.
///
/// POST /api/v1/chains/init
#[utoipa::path(
    post,
    path = "/api/v1/chains/init",
    request_body = InitChainRequest,
    responses(
        (status = 201, description = "Directive chain initialized", body = InitChainResponse),
        (status = 400, description = "Invalid request", body = ApiError),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn init_chain(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Json(req): Json<InitChainRequest>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Validate the request
    if req.goal.trim().is_empty() {
        return (
            StatusCode::BAD_REQUEST,
            Json(ApiError::new("VALIDATION_ERROR", "Goal cannot be empty")),
        )
            .into_response();
    }

    match repository::init_chain_for_owner(pool, auth.owner_id, req).await {
        Ok(response) => (StatusCode::CREATED, Json(response)).into_response(),
        Err(e) => {
            tracing::error!("Failed to initialize directive chain: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Get a chain by ID.
///
/// GET /api/v1/chains/{id}
#[utoipa::path(
    get,
    path = "/api/v1/chains/{id}",
    params(
        ("id" = Uuid, Path, description = "Chain ID")
    ),
    responses(
        (status = 200, description = "Chain with contracts", body = ChainWithContracts),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn get_chain(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(chain_id): Path<Uuid>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    match repository::get_chain_with_contracts(pool, chain_id, auth.owner_id).await {
        Ok(Some(chain)) => Json(chain).into_response(),
        Ok(None) => (
            StatusCode::NOT_FOUND,
            Json(ApiError::new("NOT_FOUND", "Chain not found")),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to get chain: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Update a chain.
///
/// PUT /api/v1/chains/{id}
#[utoipa::path(
    put,
    path = "/api/v1/chains/{id}",
    params(
        ("id" = Uuid, Path, description = "Chain ID")
    ),
    request_body = UpdateChainRequest,
    responses(
        (status = 200, description = "Chain updated"),
        (status = 400, description = "Invalid request", body = ApiError),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain not found", body = ApiError),
        (status = 409, description = "Version conflict", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn update_chain(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(chain_id): Path<Uuid>,
    Json(req): Json<UpdateChainRequest>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    match repository::update_chain_for_owner(pool, chain_id, auth.owner_id, req).await {
        Ok(chain) => Json(chain).into_response(),
        Err(RepositoryError::VersionConflict { expected, actual }) => (
            StatusCode::CONFLICT,
            Json(ApiError::new(
                "VERSION_CONFLICT",
                format!("Version conflict: expected {}, found {}", expected, actual),
            )),
        )
            .into_response(),
        Err(RepositoryError::Database(e)) => {
            // Check if it's a "row not found" error
            let error_str = e.to_string();
            if error_str.contains("no rows") || error_str.contains("RowNotFound") {
                (
                    StatusCode::NOT_FOUND,
                    Json(ApiError::new("NOT_FOUND", "Chain not found")),
                )
                    .into_response()
            } else {
                tracing::error!("Failed to update chain: {}", e);
                (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    Json(ApiError::new("DB_ERROR", e.to_string())),
                )
                    .into_response()
            }
        }
    }
}

/// Delete (archive) a chain.
///
/// DELETE /api/v1/chains/{id}
#[utoipa::path(
    delete,
    path = "/api/v1/chains/{id}",
    params(
        ("id" = Uuid, Path, description = "Chain ID")
    ),
    responses(
        (status = 200, description = "Chain archived"),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn delete_chain(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(chain_id): Path<Uuid>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    match repository::delete_chain_for_owner(pool, chain_id, auth.owner_id).await {
        Ok(true) => Json(serde_json::json!({"archived": true})).into_response(),
        Ok(false) => (
            StatusCode::NOT_FOUND,
            Json(ApiError::new("NOT_FOUND", "Chain not found")),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to delete chain: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Get contracts in a chain.
///
/// GET /api/v1/chains/{id}/contracts
#[utoipa::path(
    get,
    path = "/api/v1/chains/{id}/contracts",
    params(
        ("id" = Uuid, Path, description = "Chain ID")
    ),
    responses(
        (status = 200, description = "List of contracts in chain", body = Vec<ChainContractDetail>),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn get_chain_contracts(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(chain_id): Path<Uuid>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Verify ownership
    match repository::get_chain_for_owner(pool, chain_id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Chain not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to verify chain ownership: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    match repository::list_chain_contracts(pool, chain_id).await {
        Ok(contracts) => Json(contracts).into_response(),
        Err(e) => {
            tracing::error!("Failed to list chain contracts: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Get chain DAG structure for visualization.
///
/// GET /api/v1/chains/{id}/graph
#[utoipa::path(
    get,
    path = "/api/v1/chains/{id}/graph",
    params(
        ("id" = Uuid, Path, description = "Chain ID")
    ),
    responses(
        (status = 200, description = "Chain graph structure", body = ChainGraphResponse),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn get_chain_graph(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(chain_id): Path<Uuid>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Verify ownership first
    match repository::get_chain_for_owner(pool, chain_id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Chain not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to verify chain ownership: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    match repository::get_chain_graph(pool, chain_id).await {
        Ok(Some(graph)) => Json(graph).into_response(),
        Ok(None) => (
            StatusCode::NOT_FOUND,
            Json(ApiError::new("NOT_FOUND", "Chain not found")),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to get chain graph: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Get chain events.
///
/// GET /api/v1/chains/{id}/events
#[utoipa::path(
    get,
    path = "/api/v1/chains/{id}/events",
    params(
        ("id" = Uuid, Path, description = "Chain ID")
    ),
    responses(
        (status = 200, description = "Chain events", body = Vec<ChainEvent>),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn get_chain_events(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(chain_id): Path<Uuid>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Verify ownership
    match repository::get_chain_for_owner(pool, chain_id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Chain not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to verify chain ownership: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    match repository::list_chain_events(pool, chain_id).await {
        Ok(events) => Json(events).into_response(),
        Err(e) => {
            tracing::error!("Failed to list chain events: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Get chain editor data.
///
/// GET /api/v1/chains/{id}/editor
#[utoipa::path(
    get,
    path = "/api/v1/chains/{id}/editor",
    params(
        ("id" = Uuid, Path, description = "Chain ID")
    ),
    responses(
        (status = 200, description = "Chain editor data", body = ChainEditorData),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn get_chain_editor(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(chain_id): Path<Uuid>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    match repository::get_chain_editor_data(pool, chain_id, auth.owner_id).await {
        Ok(Some(editor_data)) => Json(editor_data).into_response(),
        Ok(None) => (
            StatusCode::NOT_FOUND,
            Json(ApiError::new("NOT_FOUND", "Chain not found")),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to get chain editor data: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

// =============================================================================
// Contract Definition Handlers
// =============================================================================

/// List contract definitions for a chain.
///
/// GET /api/v1/chains/{id}/definitions
#[utoipa::path(
    get,
    path = "/api/v1/chains/{id}/definitions",
    params(
        ("id" = Uuid, Path, description = "Chain ID")
    ),
    responses(
        (status = 200, description = "List of contract definitions", body = Vec<ChainContractDefinition>),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn list_chain_definitions(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(chain_id): Path<Uuid>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Verify ownership
    match repository::get_chain_for_owner(pool, chain_id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Chain not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to verify chain ownership: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    match repository::list_chain_contract_definitions(pool, chain_id).await {
        Ok(definitions) => Json(definitions).into_response(),
        Err(e) => {
            tracing::error!("Failed to list chain definitions: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Create a contract definition for a chain.
///
/// POST /api/v1/chains/{id}/definitions
#[utoipa::path(
    post,
    path = "/api/v1/chains/{id}/definitions",
    params(
        ("id" = Uuid, Path, description = "Chain ID")
    ),
    request_body = AddContractDefinitionRequest,
    responses(
        (status = 201, description = "Contract definition created", body = ChainContractDefinition),
        (status = 400, description = "Invalid request", body = ApiError),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn create_chain_definition(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(chain_id): Path<Uuid>,
    Json(req): Json<AddContractDefinitionRequest>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Validate the request
    if req.name.trim().is_empty() {
        return (
            StatusCode::BAD_REQUEST,
            Json(ApiError::new("VALIDATION_ERROR", "Definition name cannot be empty")),
        )
            .into_response();
    }

    // Verify ownership
    match repository::get_chain_for_owner(pool, chain_id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Chain not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to verify chain ownership: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    match repository::create_chain_contract_definition(pool, chain_id, req).await {
        Ok(definition) => (StatusCode::CREATED, Json(definition)).into_response(),
        Err(e) => {
            tracing::error!("Failed to create chain definition: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Update a contract definition.
///
/// PUT /api/v1/chains/{chain_id}/definitions/{definition_id}
#[utoipa::path(
    put,
    path = "/api/v1/chains/{chain_id}/definitions/{definition_id}",
    params(
        ("chain_id" = Uuid, Path, description = "Chain ID"),
        ("definition_id" = Uuid, Path, description = "Definition ID")
    ),
    request_body = UpdateContractDefinitionRequest,
    responses(
        (status = 200, description = "Contract definition updated", body = ChainContractDefinition),
        (status = 400, description = "Invalid request", body = ApiError),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain or definition not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn update_chain_definition(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path((chain_id, definition_id)): Path<(Uuid, Uuid)>,
    Json(req): Json<UpdateContractDefinitionRequest>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Verify ownership
    match repository::get_chain_for_owner(pool, chain_id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Chain not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to verify chain ownership: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    // Verify definition belongs to this chain
    match repository::get_chain_contract_definition(pool, definition_id).await {
        Ok(Some(def)) if def.chain_id == chain_id => {}
        Ok(Some(_)) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Definition not found in this chain")),
            )
                .into_response();
        }
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Definition not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to get chain definition: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    match repository::update_chain_contract_definition(pool, definition_id, req).await {
        Ok(definition) => Json(definition).into_response(),
        Err(e) => {
            tracing::error!("Failed to update chain definition: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Delete a contract definition.
///
/// DELETE /api/v1/chains/{chain_id}/definitions/{definition_id}
#[utoipa::path(
    delete,
    path = "/api/v1/chains/{chain_id}/definitions/{definition_id}",
    params(
        ("chain_id" = Uuid, Path, description = "Chain ID"),
        ("definition_id" = Uuid, Path, description = "Definition ID")
    ),
    responses(
        (status = 200, description = "Contract definition deleted"),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain or definition not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn delete_chain_definition(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path((chain_id, definition_id)): Path<(Uuid, Uuid)>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Verify ownership
    match repository::get_chain_for_owner(pool, chain_id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Chain not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to verify chain ownership: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    // Verify definition belongs to this chain before deleting
    match repository::get_chain_contract_definition(pool, definition_id).await {
        Ok(Some(def)) if def.chain_id == chain_id => {}
        Ok(Some(_)) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Definition not found in this chain")),
            )
                .into_response();
        }
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Definition not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to get chain definition: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    match repository::delete_chain_contract_definition(pool, definition_id).await {
        Ok(true) => Json(serde_json::json!({"deleted": true})).into_response(),
        Ok(false) => (
            StatusCode::NOT_FOUND,
            Json(ApiError::new("NOT_FOUND", "Definition not found")),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to delete chain definition: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Get definition graph for a chain (shows definitions + instantiation status).
///
/// GET /api/v1/chains/{id}/definitions/graph
#[utoipa::path(
    get,
    path = "/api/v1/chains/{id}/definitions/graph",
    params(
        ("id" = Uuid, Path, description = "Chain ID")
    ),
    responses(
        (status = 200, description = "Definition graph structure", body = ChainDefinitionGraphResponse),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn get_chain_definition_graph(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(chain_id): Path<Uuid>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Verify ownership first
    match repository::get_chain_for_owner(pool, chain_id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Chain not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to verify chain ownership: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    match repository::get_chain_definition_graph(pool, chain_id).await {
        Ok(Some(graph)) => Json(graph).into_response(),
        Ok(None) => (
            StatusCode::NOT_FOUND,
            Json(ApiError::new("NOT_FOUND", "Chain not found")),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to get chain definition graph: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

// =============================================================================
// Chain Control Handlers
// =============================================================================

/// Start a chain (spawns supervisor and creates root contracts).
///
/// POST /api/v1/chains/{id}/start
#[utoipa::path(
    post,
    path = "/api/v1/chains/{id}/start",
    params(
        ("id" = Uuid, Path, description = "Chain ID")
    ),
    request_body(content = Option<StartChainRequest>, description = "Optional start options"),
    responses(
        (status = 200, description = "Chain started", body = StartChainResponse),
        (status = 400, description = "Chain cannot be started", body = ApiError),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn start_chain(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(chain_id): Path<Uuid>,
    _body: Option<Json<StartChainRequest>>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Verify ownership and get chain
    let chain = match repository::get_chain_for_owner(pool, chain_id, auth.owner_id).await {
        Ok(Some(c)) => c,
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Chain not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to get chain: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    };

    // Check if chain can be started
    if chain.status == "active" {
        return (
            StatusCode::BAD_REQUEST,
            Json(ApiError::new("ALREADY_ACTIVE", "Chain is already active")),
        )
            .into_response();
    }
    if chain.status == "completed" {
        return (
            StatusCode::BAD_REQUEST,
            Json(ApiError::new("ALREADY_COMPLETED", "Chain is already completed")),
        )
            .into_response();
    }

    // Get definitions to check if there are any
    let definitions = match repository::list_chain_contract_definitions(pool, chain_id).await {
        Ok(d) => d,
        Err(e) => {
            tracing::error!("Failed to list chain definitions: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    };

    if definitions.is_empty() {
        return (
            StatusCode::BAD_REQUEST,
            Json(ApiError::new("NO_DEFINITIONS", "Chain has no contract definitions")),
        )
            .into_response();
    }

    // Update chain status to active
    match repository::update_chain_status(pool, chain_id, "active").await {
        Ok(_) => {}
        Err(e) => {
            tracing::error!("Failed to update chain status: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    // Progress the chain - this creates root contracts (definitions with no dependencies)
    let progression = match repository::progress_chain(pool, chain_id, auth.owner_id).await {
        Ok(p) => p,
        Err(e) => {
            tracing::error!("Failed to progress chain: {}", e);
            // Chain is active but no contracts created - return partial success
            return Json(StartChainResponse {
                chain_id,
                contracts_created: vec![],
                status: "active".to_string(),
            })
            .into_response();
        }
    };

    Json(StartChainResponse {
        chain_id,
        contracts_created: progression.contracts_created,
        status: if progression.chain_completed {
            "completed".to_string()
        } else {
            "active".to_string()
        },
    })
    .into_response()
}

/// Stop a chain (kills supervisor, marks as archived).
///
/// POST /api/v1/chains/{id}/stop
#[utoipa::path(
    post,
    path = "/api/v1/chains/{id}/stop",
    params(
        ("id" = Uuid, Path, description = "Chain ID")
    ),
    responses(
        (status = 200, description = "Chain stopped"),
        (status = 400, description = "Chain cannot be stopped", body = ApiError),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn stop_chain(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(chain_id): Path<Uuid>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Verify ownership and get chain
    let chain = match repository::get_chain_for_owner(pool, chain_id, auth.owner_id).await {
        Ok(Some(c)) => c,
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Chain not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to get chain: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    };

    // Check if chain can be stopped
    if chain.status != "active" {
        return (
            StatusCode::BAD_REQUEST,
            Json(ApiError::new(
                "NOT_ACTIVE",
                format!("Chain is not active (status: {})", chain.status),
            )),
        )
            .into_response();
    }

    // Archive the chain
    match repository::update_chain_status(pool, chain_id, "archived").await {
        Ok(_) => Json(serde_json::json!({"stopped": true, "status": "archived"})).into_response(),
        Err(e) => {
            tracing::error!("Failed to update chain status: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

// =============================================================================
// Chain Repository Handlers
// =============================================================================

/// List repositories for a chain.
///
/// GET /api/v1/chains/{id}/repositories
#[utoipa::path(
    get,
    path = "/api/v1/chains/{id}/repositories",
    params(
        ("id" = Uuid, Path, description = "Chain ID")
    ),
    responses(
        (status = 200, description = "List of repositories", body = Vec<ChainRepository>),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn list_chain_repositories(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(chain_id): Path<Uuid>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Verify ownership
    match repository::get_chain_for_owner(pool, chain_id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Chain not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to verify chain ownership: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    match repository::list_chain_repositories(pool, chain_id).await {
        Ok(repos) => Json(repos).into_response(),
        Err(e) => {
            tracing::error!("Failed to list chain repositories: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Add a repository to a chain.
///
/// POST /api/v1/chains/{id}/repositories
#[utoipa::path(
    post,
    path = "/api/v1/chains/{id}/repositories",
    params(
        ("id" = Uuid, Path, description = "Chain ID")
    ),
    request_body = AddChainRepositoryRequest,
    responses(
        (status = 201, description = "Repository added", body = ChainRepository),
        (status = 400, description = "Invalid request", body = ApiError),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn add_chain_repository(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(chain_id): Path<Uuid>,
    Json(req): Json<AddChainRepositoryRequest>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Validate request
    if req.name.trim().is_empty() {
        return (
            StatusCode::BAD_REQUEST,
            Json(ApiError::new("VALIDATION_ERROR", "Repository name cannot be empty")),
        )
            .into_response();
    }

    // Must have either repository_url or local_path
    if req.repository_url.is_none() && req.local_path.is_none() {
        return (
            StatusCode::BAD_REQUEST,
            Json(ApiError::new(
                "VALIDATION_ERROR",
                "Repository must have either repository_url or local_path",
            )),
        )
            .into_response();
    }

    // Verify ownership
    match repository::get_chain_for_owner(pool, chain_id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Chain not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to verify chain ownership: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    match repository::add_chain_repository(pool, chain_id, &req).await {
        Ok(repo) => (StatusCode::CREATED, Json(repo)).into_response(),
        Err(e) => {
            tracing::error!("Failed to add chain repository: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Delete a repository from a chain.
///
/// DELETE /api/v1/chains/{chain_id}/repositories/{repository_id}
#[utoipa::path(
    delete,
    path = "/api/v1/chains/{chain_id}/repositories/{repository_id}",
    params(
        ("chain_id" = Uuid, Path, description = "Chain ID"),
        ("repository_id" = Uuid, Path, description = "Repository ID")
    ),
    responses(
        (status = 200, description = "Repository deleted"),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain or repository not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn delete_chain_repository(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path((chain_id, repository_id)): Path<(Uuid, Uuid)>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Verify ownership
    match repository::get_chain_for_owner(pool, chain_id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Chain not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to verify chain ownership: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    match repository::delete_chain_repository(pool, chain_id, repository_id).await {
        Ok(true) => Json(serde_json::json!({"deleted": true})).into_response(),
        Ok(false) => (
            StatusCode::NOT_FOUND,
            Json(ApiError::new("NOT_FOUND", "Repository not found")),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to delete chain repository: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Set a repository as primary for a chain.
///
/// PUT /api/v1/chains/{chain_id}/repositories/{repository_id}/primary
#[utoipa::path(
    put,
    path = "/api/v1/chains/{chain_id}/repositories/{repository_id}/primary",
    params(
        ("chain_id" = Uuid, Path, description = "Chain ID"),
        ("repository_id" = Uuid, Path, description = "Repository ID")
    ),
    responses(
        (status = 200, description = "Repository set as primary", body = ChainRepository),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Chain or repository not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
        (status = 500, description = "Internal server error", body = ApiError)
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Chains"
)]
pub async fn set_chain_repository_primary(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path((chain_id, repository_id)): Path<(Uuid, Uuid)>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Verify ownership
    match repository::get_chain_for_owner(pool, chain_id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Chain not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to verify chain ownership: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    // Verify repository exists for this chain
    match repository::get_chain_repository(pool, chain_id, repository_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Repository not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to get chain repository: {}", e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    match repository::set_chain_repository_primary(pool, chain_id, repository_id).await {
        Ok(repo) => Json(repo).into_response(),
        Err(e) => {
            tracing::error!("Failed to set chain repository as primary: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}
