// Proposals API endpoints for approval gates

use axum::{
    extract::{Path, Query, State},
    http::StatusCode,
    response::IntoResponse,
    Json,
};
use ontoref_derive::onto_api;
use serde::Deserialize;
use vapora_channels::Message;
use vapora_shared::models::{Proposal, ProposalReview, ProposalStatus, RiskLevel};

use crate::api::state::AppState;
use crate::api::ApiResult;

#[derive(Debug, Deserialize)]
pub struct ProposalQueryParams {
    pub project_id: String,
    pub status: Option<String>,
    #[allow(dead_code)]
    pub risk_level: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct CreateProposalPayload {
    pub project_id: String,
    pub task_id: String,
    pub agent_id: String,
    pub title: String,
    pub description: Option<String>,
    pub risk_level: String,
    pub plan_details: serde_json::Value,
}

#[derive(Debug, Deserialize)]
pub struct AddReviewPayload {
    pub reviewer_id: String,
    pub feedback: String,
    pub approved: bool,
}

/// List proposals with optional filters
///
/// GET /api/v1/proposals?project_id=xxx&status=proposed
#[onto_api(
    method = "GET",
    path = "/api/v1/proposals",
    description = "Returns proposals for a project, optionally filtered by status.",
    auth = "bearer",
    actors = "developer, agent, ci, admin",
    params = "project_id:string:required:Parent project identifier; status:string:optional:Filter \
              by proposal status",
    tags = "proposals, read"
)]
pub async fn list_proposals(
    State(state): State<AppState>,
    Query(params): Query<ProposalQueryParams>,
) -> ApiResult<impl IntoResponse> {
    let tenant_id = "default";

    let status = params.status.as_ref().map(|s| match s.as_str() {
        "proposed" => ProposalStatus::Proposed,
        "approved" => ProposalStatus::Approved,
        "rejected" => ProposalStatus::Rejected,
        "executed" => ProposalStatus::Executed,
        _ => ProposalStatus::Proposed,
    });

    let proposals = state
        .proposal_service
        .list_proposals(&params.project_id, tenant_id, status)
        .await?;

    Ok(Json(proposals))
}

/// Get a specific proposal
///
/// GET /api/v1/proposals/:id
#[onto_api(
    method = "GET",
    path = "/api/v1/proposals/:id",
    description = "Returns a single proposal by its identifier.",
    auth = "bearer",
    actors = "developer, agent, ci, admin",
    params = "id:string:required:Proposal identifier",
    tags = "proposals, read"
)]
pub async fn get_proposal(
    State(state): State<AppState>,
    Path(id): Path<String>,
) -> ApiResult<impl IntoResponse> {
    let tenant_id = "default";

    let proposal = state.proposal_service.get_proposal(&id, tenant_id).await?;

    Ok(Json(proposal))
}

/// Create a new proposal
///
/// POST /api/v1/proposals
#[onto_api(
    method = "POST",
    path = "/api/v1/proposals",
    description = "Creates a new change proposal with risk level and execution plan.",
    auth = "bearer",
    actors = "agent, developer, admin",
    tags = "proposals, write"
)]
pub async fn create_proposal(
    State(state): State<AppState>,
    Json(payload): Json<CreateProposalPayload>,
) -> ApiResult<impl IntoResponse> {
    let tenant_id = "default";

    let risk_level = match payload.risk_level.as_str() {
        "low" => RiskLevel::Low,
        "medium" => RiskLevel::Medium,
        "high" => RiskLevel::High,
        _ => RiskLevel::Low,
    };

    let plan_details: vapora_shared::models::PlanDetails =
        serde_json::from_value(payload.plan_details).map_err(|_| {
            vapora_shared::VaporaError::InvalidInput("Invalid plan details".to_string())
        })?;

    let proposal = Proposal {
        id: None,
        tenant_id: tenant_id.to_string(),
        project_id: payload.project_id,
        task_id: payload.task_id,
        agent_id: payload.agent_id,
        title: payload.title,
        description: payload.description,
        status: ProposalStatus::Proposed,
        risk_level,
        plan_details,
        created_at: chrono::Utc::now(),
        submitted_at: None,
        reviewed_at: None,
        executed_at: None,
    };

    let created = state.proposal_service.create_proposal(proposal).await?;

    Ok((StatusCode::CREATED, Json(created)))
}

/// Update proposal
///
/// PUT /api/v1/proposals/:id
#[onto_api(
    method = "PUT",
    path = "/api/v1/proposals/:id",
    description = "Fully replaces a proposal record with the provided payload.",
    auth = "bearer",
    actors = "agent, developer, admin",
    params = "id:string:required:Proposal identifier",
    tags = "proposals, write"
)]
pub async fn update_proposal(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Json(payload): Json<Proposal>,
) -> ApiResult<impl IntoResponse> {
    let tenant_id = "default";

    let updated = state
        .proposal_service
        .update_proposal(&id, tenant_id, payload)
        .await?;

    Ok(Json(updated))
}

/// Delete proposal
///
/// DELETE /api/v1/proposals/:id
#[onto_api(
    method = "DELETE",
    path = "/api/v1/proposals/:id",
    description = "Permanently removes a proposal by its identifier.",
    auth = "bearer",
    actors = "developer, admin",
    params = "id:string:required:Proposal identifier",
    tags = "proposals, write"
)]
pub async fn delete_proposal(
    State(state): State<AppState>,
    Path(id): Path<String>,
) -> ApiResult<impl IntoResponse> {
    let tenant_id = "default";

    state
        .proposal_service
        .delete_proposal(&id, tenant_id)
        .await?;

    Ok(StatusCode::NO_CONTENT)
}

/// Submit proposal for approval
///
/// PUT /api/v1/proposals/:id/submit
#[onto_api(
    method = "PUT",
    path = "/api/v1/proposals/:id/submit",
    description = "Submits a draft proposal into the approval workflow.",
    auth = "bearer",
    actors = "agent, developer",
    params = "id:string:required:Proposal identifier",
    tags = "proposals, write"
)]
pub async fn submit_proposal(
    State(state): State<AppState>,
    Path(id): Path<String>,
) -> ApiResult<impl IntoResponse> {
    let tenant_id = "default";

    let proposal = state
        .proposal_service
        .submit_proposal(&id, tenant_id)
        .await?;

    Ok(Json(proposal))
}

/// Approve proposal
///
/// PUT /api/v1/proposals/:id/approve
#[onto_api(
    method = "PUT",
    path = "/api/v1/proposals/:id/approve",
    description = "Approves a submitted proposal and fires approval notifications.",
    auth = "bearer",
    actors = "developer, admin",
    params = "id:string:required:Proposal identifier",
    tags = "proposals, write"
)]
pub async fn approve_proposal(
    State(state): State<AppState>,
    Path(id): Path<String>,
) -> ApiResult<impl IntoResponse> {
    let tenant_id = "default";

    let proposal = state
        .proposal_service
        .approve_proposal(&id, tenant_id)
        .await?;

    let msg = Message::success(
        "Proposal approved",
        format!("'{}' has been approved", proposal.title),
    );
    state.notify(&state.notification_config.clone().on_proposal_approved, msg);

    Ok(Json(proposal))
}

/// Reject proposal
///
/// PUT /api/v1/proposals/:id/reject
#[onto_api(
    method = "PUT",
    path = "/api/v1/proposals/:id/reject",
    description = "Rejects a submitted proposal and fires rejection notifications.",
    auth = "bearer",
    actors = "developer, admin",
    params = "id:string:required:Proposal identifier",
    tags = "proposals, write"
)]
pub async fn reject_proposal(
    State(state): State<AppState>,
    Path(id): Path<String>,
) -> ApiResult<impl IntoResponse> {
    let tenant_id = "default";

    let proposal = state
        .proposal_service
        .reject_proposal(&id, tenant_id)
        .await?;

    let msg = Message::warning(
        "Proposal rejected",
        format!("'{}' has been rejected", proposal.title),
    );
    state.notify(&state.notification_config.clone().on_proposal_rejected, msg);

    Ok(Json(proposal))
}

/// Mark proposal as executed
///
/// PUT /api/v1/proposals/:id/executed
#[onto_api(
    method = "PUT",
    path = "/api/v1/proposals/:id/executed",
    description = "Marks an approved proposal as executed after the change has been applied.",
    auth = "bearer",
    actors = "agent, admin",
    params = "id:string:required:Proposal identifier",
    tags = "proposals, write"
)]
pub async fn mark_executed(
    State(state): State<AppState>,
    Path(id): Path<String>,
) -> ApiResult<impl IntoResponse> {
    let tenant_id = "default";

    let proposal = state.proposal_service.mark_executed(&id, tenant_id).await?;

    Ok(Json(proposal))
}

/// List reviews for proposal
///
/// GET /api/v1/proposals/:id/reviews
#[onto_api(
    method = "GET",
    path = "/api/v1/proposals/:id/reviews",
    description = "Returns all reviews attached to the specified proposal.",
    auth = "bearer",
    actors = "developer, agent, ci, admin",
    params = "id:string:required:Proposal identifier",
    tags = "proposals, reviews, read"
)]
pub async fn list_reviews(
    State(state): State<AppState>,
    Path(id): Path<String>,
) -> ApiResult<impl IntoResponse> {
    let reviews = state.proposal_service.list_reviews(&id).await?;

    Ok(Json(reviews))
}

/// Add review to proposal
///
/// POST /api/v1/proposals/:id/reviews
#[onto_api(
    method = "POST",
    path = "/api/v1/proposals/:id/reviews",
    description = "Adds a reviewer decision (approve/reject with feedback) to a proposal.",
    auth = "bearer",
    actors = "developer, admin",
    params = "id:string:required:Proposal identifier",
    tags = "proposals, reviews, write"
)]
pub async fn add_review(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Json(payload): Json<AddReviewPayload>,
) -> ApiResult<impl IntoResponse> {
    let review = ProposalReview {
        id: None,
        proposal_id: id.clone(),
        reviewer_id: payload.reviewer_id,
        feedback: payload.feedback,
        approved: payload.approved,
        created_at: chrono::Utc::now(),
    };

    let created = state.proposal_service.add_review(&id, review).await?;

    Ok((StatusCode::CREATED, Json(created)))
}
