// Tasks API endpoints

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::{Task, TaskPriority, TaskStatus};
use vapora_shared::VaporaError;

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

fn injection_rejection(e: impl std::fmt::Display) -> ApiError {
    ApiError(VaporaError::InvalidInput(format!(
        "Input rejected by security scanner: {}",
        e
    )))
}

fn scan_task_text(task: &Task) -> Result<(), ApiError> {
    crate::security::prompt_injection::scan(&task.title).map_err(injection_rejection)?;
    if let Some(desc) = &task.description {
        crate::security::prompt_injection::scan(desc).map_err(injection_rejection)?;
    }
    Ok(())
}

#[derive(Debug, Deserialize)]
pub struct TaskQueryParams {
    pub project_id: String,
    pub status: Option<String>,
    pub assignee: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct ReorderTaskPayload {
    pub task_order: i32,
    pub status: Option<TaskStatus>,
}

#[derive(Debug, Deserialize)]
pub struct AssignTaskPayload {
    pub assignee: String,
}

#[derive(Debug, Deserialize)]
pub struct UpdatePriorityPayload {
    pub priority: TaskPriority,
}

/// List tasks with optional filters
///
/// GET /api/v1/tasks?project_id=xxx&status=todo&assignee=agent1
#[onto_api(
    method = "GET",
    path = "/api/v1/tasks",
    description = "Returns tasks for a project, optionally filtered by status or assignee.",
    auth = "bearer",
    actors = "developer, agent, ci, admin",
    params = "project_id:string:required:Parent project identifier; status:string:optional:Filter \
              by task status; assignee:string:optional:Filter by assignee",
    tags = "tasks, read"
)]
pub async fn list_tasks(
    State(state): State<AppState>,
    Query(params): Query<TaskQueryParams>,
) -> ApiResult<impl IntoResponse> {
    // TODO: Extract tenant_id from JWT token
    let tenant_id = "default";

    let tasks = if let Some(status_str) = params.status {
        // Parse status
        let status: TaskStatus = serde_json::from_value(serde_json::json!(status_str))?;
        state
            .task_service
            .list_tasks_by_status(&params.project_id, tenant_id, status)
            .await?
    } else if let Some(assignee) = params.assignee {
        state
            .task_service
            .list_tasks_by_assignee(&params.project_id, tenant_id, &assignee)
            .await?
    } else {
        state
            .task_service
            .list_tasks(&params.project_id, tenant_id)
            .await?
    };

    Ok(Json(tasks))
}

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

    let task = state.task_service.get_task(&id, tenant_id).await?;
    Ok(Json(task))
}

/// Create a new task
///
/// POST /api/v1/tasks
#[onto_api(
    method = "POST",
    path = "/api/v1/tasks",
    description = "Creates a new task, scanning input for prompt-injection before persisting.",
    auth = "bearer",
    actors = "developer, agent, admin",
    tags = "tasks, write"
)]
pub async fn create_task(
    State(state): State<AppState>,
    Json(mut task): Json<Task>,
) -> ApiResult<impl IntoResponse> {
    scan_task_text(&task)?;
    task.tenant_id = "default".to_string();

    let created = state.task_service.create_task(task).await?;
    Ok((StatusCode::CREATED, Json(created)))
}

/// Update a task
///
/// PUT /api/v1/tasks/:id
#[onto_api(
    method = "PUT",
    path = "/api/v1/tasks/:id",
    description = "Fully replaces a task record after prompt-injection scan.",
    auth = "bearer",
    actors = "developer, agent, admin",
    params = "id:string:required:Task identifier",
    tags = "tasks, write"
)]
pub async fn update_task(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Json(updates): Json<Task>,
) -> ApiResult<impl IntoResponse> {
    scan_task_text(&updates)?;
    let tenant_id = "default";

    let updated = state
        .task_service
        .update_task(&id, tenant_id, updates)
        .await?;
    Ok(Json(updated))
}

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

    state.task_service.delete_task(&id, tenant_id).await?;
    Ok(StatusCode::NO_CONTENT)
}

/// Reorder a task (for Kanban drag & drop)
///
/// PUT /api/v1/tasks/:id/reorder
#[onto_api(
    method = "PUT",
    path = "/api/v1/tasks/:id/reorder",
    description = "Updates a task's board position and optionally its status for Kanban \
                   drag-and-drop.",
    auth = "bearer",
    actors = "developer, agent, admin",
    params = "id:string:required:Task identifier",
    tags = "tasks, write"
)]
pub async fn reorder_task(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Json(payload): Json<ReorderTaskPayload>,
) -> ApiResult<impl IntoResponse> {
    // TODO: Extract tenant_id from JWT token
    let tenant_id = "default";

    let updated = state
        .task_service
        .reorder_task(&id, tenant_id, payload.task_order, payload.status)
        .await?;
    Ok(Json(updated))
}

/// Update task status
///
/// PUT /api/v1/tasks/:id/status
#[onto_api(
    method = "PUT",
    path = "/api/v1/tasks/:id/status",
    description = "Transitions a task to a new status and triggers notifications on completion.",
    auth = "bearer",
    actors = "developer, agent, admin",
    params = "id:string:required:Task identifier",
    tags = "tasks, write"
)]
pub async fn update_task_status(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Json(payload): Json<serde_json::Value>,
) -> ApiResult<impl IntoResponse> {
    // TODO: Extract tenant_id from JWT token
    let tenant_id = "default";

    let status: TaskStatus = serde_json::from_value(payload["status"].clone())?;

    let updated = state
        .task_service
        .update_task_status(&id, tenant_id, status.clone())
        .await?;

    if status == TaskStatus::Done {
        let msg = Message::success(
            "Task completed",
            format!("'{}' moved to Done", updated.title),
        );
        state.notify(&state.notification_config.clone().on_task_done, msg);
    }

    Ok(Json(updated))
}

/// Assign a task to an agent/user
///
/// PUT /api/v1/tasks/:id/assign
#[onto_api(
    method = "PUT",
    path = "/api/v1/tasks/:id/assign",
    description = "Assigns a task to the specified agent or user.",
    auth = "bearer",
    actors = "developer, agent, admin",
    params = "id:string:required:Task identifier",
    tags = "tasks, write"
)]
pub async fn assign_task(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Json(payload): Json<AssignTaskPayload>,
) -> ApiResult<impl IntoResponse> {
    // TODO: Extract tenant_id from JWT token
    let tenant_id = "default";

    let updated = state
        .task_service
        .assign_task(&id, tenant_id, payload.assignee)
        .await?;
    Ok(Json(updated))
}

/// Update task priority
///
/// PUT /api/v1/tasks/:id/priority
#[onto_api(
    method = "PUT",
    path = "/api/v1/tasks/:id/priority",
    description = "Updates the priority level of the specified task.",
    auth = "bearer",
    actors = "developer, agent, admin",
    params = "id:string:required:Task identifier",
    tags = "tasks, write"
)]
pub async fn update_priority(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Json(payload): Json<UpdatePriorityPayload>,
) -> ApiResult<impl IntoResponse> {
    // TODO: Extract tenant_id from JWT token
    let tenant_id = "default";

    let updated = state
        .task_service
        .update_priority(&id, tenant_id, payload.priority)
        .await?;
    Ok(Json(updated))
}
