//! HTTP handlers for daemon-to-contract interaction.
//!
//! These endpoints allow tasks running in daemons to interact with their
//! associated contracts via the contract.sh script. Authentication is via
//! tool keys registered by the daemon when starting a task.

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

use crate::db::{models::FileSummary, repository};
use crate::llm::phase_guidance::{self, FileInfo, PhaseChecklist, TaskInfo};
use crate::server::auth::Authenticated;
use crate::server::messages::ApiError;
use crate::server::state::SharedState;

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

/// Contract status response for daemon.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractStatusResponse {
    pub id: Uuid,
    pub name: String,
    pub phase: String,
    pub status: String,
    pub description: Option<String>,
}

/// Contract goals response.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractGoalsResponse {
    /// Description serves as goals for the contract
    pub description: Option<String>,
    pub phase: String,
    pub phase_guidance: String,
}

/// Progress report request from daemon.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ProgressReportRequest {
    pub message: String,
    #[serde(default)]
    pub task_id: Option<Uuid>,
}

/// Suggested action from server.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SuggestedActionResponse {
    pub action: String,
    pub description: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<serde_json::Value>,
}

/// Completion action request.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CompletionActionRequest {
    #[serde(default)]
    pub task_id: Option<Uuid>,
    #[serde(default)]
    pub files_modified: Vec<String>,
    #[serde(default)]
    pub lines_added: i32,
    #[serde(default)]
    pub lines_removed: i32,
    #[serde(default)]
    pub has_code_changes: bool,
}

/// Recommended completion action.
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum CompletionAction {
    Branch,
    Merge,
    Pr,
    None,
}

impl std::fmt::Display for CompletionAction {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CompletionAction::Branch => write!(f, "branch"),
            CompletionAction::Merge => write!(f, "merge"),
            CompletionAction::Pr => write!(f, "pr"),
            CompletionAction::None => write!(f, "none"),
        }
    }
}

/// Completion action response.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CompletionActionResponse {
    pub action: String,
    pub reason: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub branch_name: Option<String>,
}

/// Create file request from daemon.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateFileRequest {
    pub name: String,
    pub content: String,
    #[serde(default)]
    pub template_id: Option<String>,
}

/// Update file request from daemon.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DaemonUpdateFileRequest {
    /// Content to update in the file (as markdown body element)
    pub content: String,
}

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

/// Get contract status for daemon.
#[utoipa::path(
    get,
    path = "/api/v1/contracts/{id}/daemon/status",
    params(
        ("id" = Uuid, Path, description = "Contract ID")
    ),
    responses(
        (status = 200, description = "Contract status", body = ContractStatusResponse),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Contract not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
    ),
    security(
        ("tool_key" = []),
        ("api_key" = [])
    ),
    tag = "Contract Daemon"
)]
pub async fn get_contract_status(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(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_contract_for_owner(pool, id, auth.owner_id).await {
        Ok(Some(contract)) => Json(ContractStatusResponse {
            id: contract.id,
            name: contract.name,
            phase: contract.phase,
            status: contract.status,
            description: contract.description,
        })
        .into_response(),
        Ok(None) => (
            StatusCode::NOT_FOUND,
            Json(ApiError::new("NOT_FOUND", "Contract not found")),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to get contract {}: {}", id, e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Get phase deliverables checklist.
#[utoipa::path(
    get,
    path = "/api/v1/contracts/{id}/daemon/checklist",
    params(
        ("id" = Uuid, Path, description = "Contract ID")
    ),
    responses(
        (status = 200, description = "Phase checklist", body = PhaseChecklist),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Contract not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
    ),
    security(
        ("tool_key" = []),
        ("api_key" = [])
    ),
    tag = "Contract Daemon"
)]
pub async fn get_contract_checklist(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(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();
    };

    // Get contract
    let contract = match repository::get_contract_for_owner(pool, id, auth.owner_id).await {
        Ok(Some(c)) => c,
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Contract not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to get contract {}: {}", id, e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    };

    // Get files for this contract
    let files = match repository::list_files_in_contract(pool, id, auth.owner_id).await {
        Ok(f) => f
            .into_iter()
            .map(|f| FileInfo {
                id: f.id,
                name: f.name,
                contract_phase: f.contract_phase,
            })
            .collect::<Vec<_>>(),
        Err(e) => {
            tracing::warn!("Failed to get files for contract {}: {}", id, e);
            Vec::new()
        }
    };

    // Get tasks for this contract
    let tasks = match repository::list_tasks_in_contract(pool, id, auth.owner_id).await {
        Ok(t) => t
            .into_iter()
            .map(|t| TaskInfo {
                id: t.id,
                name: t.name,
                status: t.status,
            })
            .collect::<Vec<_>>(),
        Err(e) => {
            tracing::warn!("Failed to get tasks for contract {}: {}", id, e);
            Vec::new()
        }
    };

    // Check if repository is configured
    let has_repository = match repository::list_contract_repositories(pool, id).await {
        Ok(repos) => !repos.is_empty(),
        Err(_) => false,
    };

    let checklist = phase_guidance::get_phase_checklist_for_type(&contract.phase, &files, &tasks, has_repository, &contract.contract_type);

    Json(checklist).into_response()
}

/// Get contract goals.
#[utoipa::path(
    get,
    path = "/api/v1/contracts/{id}/daemon/goals",
    params(
        ("id" = Uuid, Path, description = "Contract ID")
    ),
    responses(
        (status = 200, description = "Contract goals", body = ContractGoalsResponse),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Contract not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
    ),
    security(
        ("tool_key" = []),
        ("api_key" = [])
    ),
    tag = "Contract Daemon"
)]
pub async fn get_contract_goals(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(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_contract_for_owner(pool, id, auth.owner_id).await {
        Ok(Some(contract)) => {
            let deliverables = phase_guidance::get_phase_deliverables_for_type(&contract.phase, &contract.contract_type);
            Json(ContractGoalsResponse {
                description: contract.description,
                phase: contract.phase,
                phase_guidance: deliverables.guidance,
            })
            .into_response()
        }
        Ok(None) => (
            StatusCode::NOT_FOUND,
            Json(ApiError::new("NOT_FOUND", "Contract not found")),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to get contract {}: {}", id, e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Post progress report to contract.
#[utoipa::path(
    post,
    path = "/api/v1/contracts/{id}/daemon/report",
    params(
        ("id" = Uuid, Path, description = "Contract ID")
    ),
    request_body = ProgressReportRequest,
    responses(
        (status = 200, description = "Report received"),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Contract not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
    ),
    security(
        ("tool_key" = []),
        ("api_key" = [])
    ),
    tag = "Contract Daemon"
)]
pub async fn post_progress_report(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(id): Path<Uuid>,
    Json(req): Json<ProgressReportRequest>,
) -> 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 contract exists
    match repository::get_contract_for_owner(pool, id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Contract not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to get contract {}: {}", id, e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    // Log the report as a contract event
    let event_type = "progress_report";
    let payload = serde_json::json!({
        "message": req.message,
        "task_id": req.task_id,
    });

    if let Err(e) = repository::record_contract_event(pool, id, event_type, Some(payload)).await {
        tracing::warn!("Failed to create contract event: {}", e);
    }

    Json(serde_json::json!({"status": "received"})).into_response()
}

/// Get suggested action based on contract state.
#[utoipa::path(
    post,
    path = "/api/v1/contracts/{id}/daemon/suggest-action",
    params(
        ("id" = Uuid, Path, description = "Contract ID")
    ),
    responses(
        (status = 200, description = "Suggested action", body = SuggestedActionResponse),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Contract not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
    ),
    security(
        ("tool_key" = []),
        ("api_key" = [])
    ),
    tag = "Contract Daemon"
)]
pub async fn get_suggest_action(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(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();
    };

    // Get contract
    let contract = match repository::get_contract_for_owner(pool, id, auth.owner_id).await {
        Ok(Some(c)) => c,
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Contract not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to get contract {}: {}", id, e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    };

    // Get files and tasks for checklist
    let files = repository::list_files_in_contract(pool, id, auth.owner_id)
        .await
        .unwrap_or_default()
        .into_iter()
        .map(|f| FileInfo {
            id: f.id,
            name: f.name,
            contract_phase: f.contract_phase,
        })
        .collect::<Vec<_>>();

    let tasks = repository::list_tasks_in_contract(pool, id, auth.owner_id)
        .await
        .unwrap_or_default()
        .into_iter()
        .map(|t| TaskInfo {
            id: t.id,
            name: t.name,
            status: t.status,
        })
        .collect::<Vec<_>>();

    let has_repository = repository::list_contract_repositories(pool, id)
        .await
        .map(|r| !r.is_empty())
        .unwrap_or(false);

    let checklist = phase_guidance::get_phase_checklist_for_type(&contract.phase, &files, &tasks, has_repository, &contract.contract_type);

    // Determine suggested action based on checklist
    let (action, description) = if !checklist.suggestions.is_empty() {
        ("follow_suggestion", checklist.suggestions.first().unwrap().clone())
    } else if checklist.completion_percentage >= 100 {
        ("advance_phase", format!("Phase {} is complete, consider advancing to next phase", contract.phase))
    } else {
        ("continue", format!("Continue working on {} phase ({}% complete)", contract.phase, checklist.completion_percentage))
    };

    Json(SuggestedActionResponse {
        action: action.to_string(),
        description,
        data: None,
    })
    .into_response()
}

/// Get recommended completion action.
#[utoipa::path(
    post,
    path = "/api/v1/contracts/{id}/daemon/completion-action",
    params(
        ("id" = Uuid, Path, description = "Contract ID")
    ),
    request_body = CompletionActionRequest,
    responses(
        (status = 200, description = "Recommended completion action", body = CompletionActionResponse),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Contract not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
    ),
    security(
        ("tool_key" = []),
        ("api_key" = [])
    ),
    tag = "Contract Daemon"
)]
pub async fn get_completion_action(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(id): Path<Uuid>,
    Json(req): Json<CompletionActionRequest>,
) -> impl IntoResponse {
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
        )
            .into_response();
    };

    // Get contract
    let contract = match repository::get_contract_for_owner(pool, id, auth.owner_id).await {
        Ok(Some(c)) => c,
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Contract not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to get contract {}: {}", id, e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    };

    // Determine completion action based on phase and changes
    let has_changes = !req.files_modified.is_empty() || req.lines_added > 0 || req.lines_removed > 0;
    let has_significant_changes = req.lines_added + req.lines_removed > 50;

    let (action, reason) = match contract.phase.as_str() {
        "research" | "specify" => {
            if has_changes {
                (CompletionAction::Merge, "Early phase changes can be merged directly".to_string())
            } else {
                (CompletionAction::None, "No changes to commit".to_string())
            }
        }
        "plan" => {
            if has_significant_changes {
                (CompletionAction::Pr, "Significant planning changes require review".to_string())
            } else if has_changes {
                (CompletionAction::Merge, "Minor planning changes can be merged".to_string())
            } else {
                (CompletionAction::None, "No changes to commit".to_string())
            }
        }
        "execute" => {
            if req.has_code_changes {
                (CompletionAction::Pr, "Code changes in execute phase require review".to_string())
            } else if has_changes {
                (CompletionAction::Branch, "Documentation changes can be branched".to_string())
            } else {
                (CompletionAction::None, "No changes to commit".to_string())
            }
        }
        "review" => {
            if has_changes {
                (CompletionAction::Pr, "Review phase changes should be reviewed".to_string())
            } else {
                (CompletionAction::None, "No changes to commit".to_string())
            }
        }
        _ => (CompletionAction::None, "Unknown phase".to_string()),
    };

    // Generate branch name based on contract
    let branch_name = if matches!(action, CompletionAction::Branch | CompletionAction::Pr) {
        let slug = contract.name.to_lowercase().replace(' ', "-");
        Some(format!("contract/{}", slug))
    } else {
        None
    };

    Json(CompletionActionResponse {
        action: action.to_string(),
        reason,
        branch_name,
    })
    .into_response()
}

/// List contract files for daemon.
#[utoipa::path(
    get,
    path = "/api/v1/contracts/{id}/daemon/files",
    params(
        ("id" = Uuid, Path, description = "Contract ID")
    ),
    responses(
        (status = 200, description = "List of contract files", body = Vec<FileSummary>),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Contract not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
    ),
    security(
        ("tool_key" = []),
        ("api_key" = [])
    ),
    tag = "Contract Daemon"
)]
pub async fn list_contract_files(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(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 contract exists
    match repository::get_contract_for_owner(pool, id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Contract not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to get contract {}: {}", id, e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    match repository::list_files_in_contract(pool, id, auth.owner_id).await {
        Ok(files) => Json(files).into_response(),
        Err(e) => {
            tracing::error!("Failed to list files for contract {}: {}", id, e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Get a specific contract file.
#[utoipa::path(
    get,
    path = "/api/v1/contracts/{id}/daemon/files/{file_id}",
    params(
        ("id" = Uuid, Path, description = "Contract ID"),
        ("file_id" = Uuid, Path, description = "File ID")
    ),
    responses(
        (status = 200, description = "File content"),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Contract or file not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
    ),
    security(
        ("tool_key" = []),
        ("api_key" = [])
    ),
    tag = "Contract Daemon"
)]
pub async fn get_contract_file(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path((id, file_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 contract exists
    match repository::get_contract_for_owner(pool, id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Contract not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to get contract {}: {}", id, e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    // Get file and verify it belongs to this contract
    match repository::get_file_for_owner(pool, file_id, auth.owner_id).await {
        Ok(Some(file)) => {
            if file.contract_id != Some(id) {
                return (
                    StatusCode::NOT_FOUND,
                    Json(ApiError::new("NOT_FOUND", "File not found in this contract")),
                )
                    .into_response();
            }
            Json(file).into_response()
        }
        Ok(None) => (
            StatusCode::NOT_FOUND,
            Json(ApiError::new("NOT_FOUND", "File not found")),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to get file {}: {}", file_id, e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}

/// Update a contract file.
#[utoipa::path(
    put,
    path = "/api/v1/contracts/{id}/daemon/files/{file_id}",
    params(
        ("id" = Uuid, Path, description = "Contract ID"),
        ("file_id" = Uuid, Path, description = "File ID")
    ),
    request_body = DaemonUpdateFileRequest,
    responses(
        (status = 200, description = "File updated"),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Contract or file not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
    ),
    security(
        ("tool_key" = []),
        ("api_key" = [])
    ),
    tag = "Contract Daemon"
)]
pub async fn update_contract_file(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path((id, file_id)): Path<(Uuid, Uuid)>,
    Json(req): Json<DaemonUpdateFileRequest>,
) -> 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 contract exists
    match repository::get_contract_for_owner(pool, id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Contract not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to get contract {}: {}", id, e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    }

    // Get file and verify it belongs to this contract
    let file = match repository::get_file_for_owner(pool, file_id, auth.owner_id).await {
        Ok(Some(f)) => f,
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "File not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to get file {}: {}", file_id, e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    };

    if file.contract_id != Some(id) {
        return (
            StatusCode::NOT_FOUND,
            Json(ApiError::new("NOT_FOUND", "File not found in this contract")),
        )
            .into_response();
    }

    // Update the file with content parsed as markdown
    let body = crate::llm::markdown_to_body(&req.content);
    let update_req = crate::db::models::UpdateFileRequest {
        name: None,
        description: None,
        transcript: None,
        summary: None,
        body: Some(body),
        version: None,
        repo_file_path: None,
    };

    match repository::update_file_for_owner(pool, file_id, auth.owner_id, update_req).await {
        Ok(Some(updated)) => Json(updated).into_response(),
        Ok(None) => (
            StatusCode::NOT_FOUND,
            Json(ApiError::new("NOT_FOUND", "File not found")),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to update file {}: {}", file_id, e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", format!("{}", e))),
            )
                .into_response()
        }
    }
}

/// Create a new contract file.
#[utoipa::path(
    post,
    path = "/api/v1/contracts/{id}/daemon/files",
    params(
        ("id" = Uuid, Path, description = "Contract ID")
    ),
    request_body = CreateFileRequest,
    responses(
        (status = 201, description = "File created"),
        (status = 401, description = "Unauthorized", body = ApiError),
        (status = 404, description = "Contract not found", body = ApiError),
        (status = 503, description = "Database not configured", body = ApiError),
    ),
    security(
        ("tool_key" = []),
        ("api_key" = [])
    ),
    tag = "Contract Daemon"
)]
pub async fn create_contract_file(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Path(id): Path<Uuid>,
    Json(req): Json<CreateFileRequest>,
) -> 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 contract exists
    match repository::get_contract_for_owner(pool, id, auth.owner_id).await {
        Ok(Some(_)) => {}
        Ok(None) => {
            return (
                StatusCode::NOT_FOUND,
                Json(ApiError::new("NOT_FOUND", "Contract not found")),
            )
                .into_response();
        }
        Err(e) => {
            tracing::error!("Failed to get contract {}: {}", id, e);
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response();
        }
    };

    // Create the file with content parsed as markdown
    let body = crate::llm::markdown_to_body(&req.content);
    let create_req = crate::db::models::CreateFileRequest {
        contract_id: id,
        name: Some(req.name),
        description: None,
        transcript: vec![],
        location: None,
        body,
        repo_file_path: None,
        contract_phase: None, // Will be looked up from contract's current phase
    };

    match repository::create_file_for_owner(pool, auth.owner_id, create_req).await {
        Ok(file) => (StatusCode::CREATED, Json(file)).into_response(),
        Err(e) => {
            tracing::error!("Failed to create file for contract {}: {}", id, e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApiError::new("DB_ERROR", e.to_string())),
            )
                .into_response()
        }
    }
}
