//! Käsittelijät — CRUD-operaatiot todo-entiteetille.

use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::Json;
use sqlx::SqlitePool;

use crate::models::{CreateTodo, Todo, UpdateTodo};

/// Luo uusi tehtävä.
pub async fn create_todo(
    State(pool): State<SqlitePool>,
    Json(input): Json<CreateTodo>,
) -> Result<(StatusCode, Json<Todo>), StatusCode> {
    let priority = input.priority.unwrap_or(1);
    let status = input.status.unwrap_or_else(|| "pending".to_string());

    let result = sqlx::query_as::<_, Todo>(
        "INSERT INTO todos (title, description, due_date, priority, status)
         VALUES (?, ?, ?, ?, ?)
         RETURNING id, title, description, due_date, priority, status",
    )
    .bind(&input.title)
    .bind(&input.description)
    .bind(&input.due_date)
    .bind(priority)
    .bind(&status)
    .fetch_one(&pool)
    .await
    .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

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

/// Listaa kaikki tehtävät.
pub async fn list_todos(
    State(pool): State<SqlitePool>,
) -> Result<Json<Vec<Todo>>, StatusCode> {
    let todos = sqlx::query_as::<_, Todo>("SELECT id, title, description, due_date, priority, status FROM todos")
        .fetch_all(&pool)
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    Ok(Json(todos))
}

/// Hae tehtävä id:llä.
pub async fn get_todo(
    State(pool): State<SqlitePool>,
    Path(id): Path<i64>,
) -> Result<Json<Todo>, StatusCode> {
    let todo = sqlx::query_as::<_, Todo>(
        "SELECT id, title, description, due_date, priority, status FROM todos WHERE id = ?",
    )
    .bind(id)
    .fetch_optional(&pool)
    .await
    .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    match todo {
        Some(t) => Ok(Json(t)),
        None => Err(StatusCode::NOT_FOUND),
    }
}

/// Päivitä tehtävä id:llä.
pub async fn update_todo(
    State(pool): State<SqlitePool>,
    Path(id): Path<i64>,
    Json(input): Json<UpdateTodo>,
) -> Result<Json<Todo>, StatusCode> {
    let existing = sqlx::query_as::<_, Todo>(
        "SELECT id, title, description, due_date, priority, status FROM todos WHERE id = ?",
    )
    .bind(id)
    .fetch_optional(&pool)
    .await
    .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    let existing = existing.ok_or(StatusCode::NOT_FOUND)?;

    let title = input.title.unwrap_or(existing.title);
    let description = input.description.or(existing.description);
    let due_date = input.due_date.or(existing.due_date);
    let priority = input.priority.unwrap_or(existing.priority);
    let status = input.status.unwrap_or(existing.status);

    let updated = sqlx::query_as::<_, Todo>(
        "UPDATE todos SET title = ?, description = ?, due_date = ?, priority = ?, status = ?
         WHERE id = ?
         RETURNING id, title, description, due_date, priority, status",
    )
    .bind(&title)
    .bind(&description)
    .bind(&due_date)
    .bind(priority)
    .bind(&status)
    .bind(id)
    .fetch_one(&pool)
    .await
    .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    Ok(Json(updated))
}

/// Poista tehtävä id:llä.
pub async fn delete_todo(
    State(pool): State<SqlitePool>,
    Path(id): Path<i64>,
) -> Result<StatusCode, StatusCode> {
    let result = sqlx::query("DELETE FROM todos WHERE id = ?")
        .bind(id)
        .execute(&pool)
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    if result.rows_affected() == 0 {
        return Err(StatusCode::NOT_FOUND);
    }

    Ok(StatusCode::NO_CONTENT)
}
