//! Integraatiotestit — muistinvarainen SQLite, uniikki data per testi.

use axum::http::StatusCode;
use reqwest::Client;
use sqlx::sqlite::SqlitePoolOptions;
use todo_rs::{app, init_db};

/// Käynnistä testipalvelin satunnaisessa portissa.
async fn spawn_server() -> (Client, String) {
    let pool = SqlitePoolOptions::new()
        .max_connections(1)
        .connect("sqlite::memory:")
        .await
        .expect("Testitietokanta epäonnistui");

    init_db(&pool).await;

    let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
        .await
        .expect("Testiportin kuuntelu epäonnistui");
    let addr = listener.local_addr().unwrap();
    let base_url = format!("http://{addr}");

    let router = app(pool);
    tokio::spawn(async move {
        axum::serve(listener, router).await.unwrap();
    });

    (Client::new(), base_url)
}

#[tokio::test]
async fn test_create_todo() {
    let (client, url) = spawn_server().await;

    let res = client
        .post(format!("{url}/todos"))
        .json(&serde_json::json!({"title": "Osta maitoa", "priority": 2}))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), StatusCode::CREATED);
    let body: serde_json::Value = res.json().await.unwrap();
    assert_eq!(body["title"], "Osta maitoa");
    assert_eq!(body["priority"], 2);
    assert!(body["id"].is_number());
}

#[tokio::test]
async fn test_create_todo_defaults() {
    let (client, url) = spawn_server().await;

    let res = client
        .post(format!("{url}/todos"))
        .json(&serde_json::json!({"title": "Oletusarvotesti"}))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), StatusCode::CREATED);
    let body: serde_json::Value = res.json().await.unwrap();
    assert_eq!(body["priority"], 1);
    assert_eq!(body["status"], "pending");
    assert!(body["description"].is_null());
}

#[tokio::test]
async fn test_list_todos() {
    let (client, url) = spawn_server().await;

    client
        .post(format!("{url}/todos"))
        .json(&serde_json::json!({"title": "Listattava tehtävä"}))
        .send()
        .await
        .unwrap();

    let res = client.get(format!("{url}/todos")).send().await.unwrap();
    assert_eq!(res.status(), StatusCode::OK);

    let body: Vec<serde_json::Value> = res.json().await.unwrap();
    assert!(body.len() >= 1);
}

#[tokio::test]
async fn test_get_todo_by_id() {
    let (client, url) = spawn_server().await;

    let created: serde_json::Value = client
        .post(format!("{url}/todos"))
        .json(&serde_json::json!({"title": "Haettava tehtävä"}))
        .send()
        .await
        .unwrap()
        .json()
        .await
        .unwrap();

    let id = created["id"].as_i64().unwrap();
    let res = client
        .get(format!("{url}/todos/{id}"))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), StatusCode::OK);
    let body: serde_json::Value = res.json().await.unwrap();
    assert_eq!(body["id"], id);
    assert_eq!(body["title"], "Haettava tehtävä");
}

#[tokio::test]
async fn test_get_todo_not_found() {
    let (client, url) = spawn_server().await;

    let res = client
        .get(format!("{url}/todos/99999"))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), StatusCode::NOT_FOUND);
}

#[tokio::test]
async fn test_update_todo() {
    let (client, url) = spawn_server().await;

    let created: serde_json::Value = client
        .post(format!("{url}/todos"))
        .json(&serde_json::json!({"title": "Vanha otsikko"}))
        .send()
        .await
        .unwrap()
        .json()
        .await
        .unwrap();

    let id = created["id"].as_i64().unwrap();
    let res = client
        .put(format!("{url}/todos/{id}"))
        .json(&serde_json::json!({"title": "Uusi otsikko"}))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), StatusCode::OK);
    let body: serde_json::Value = res.json().await.unwrap();
    assert_eq!(body["title"], "Uusi otsikko");
}

#[tokio::test]
async fn test_update_todo_not_found() {
    let (client, url) = spawn_server().await;

    let res = client
        .put(format!("{url}/todos/99999"))
        .json(&serde_json::json!({"title": "Ei löydy"}))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), StatusCode::NOT_FOUND);
}

#[tokio::test]
async fn test_delete_todo() {
    let (client, url) = spawn_server().await;

    let created: serde_json::Value = client
        .post(format!("{url}/todos"))
        .json(&serde_json::json!({"title": "Poistettava"}))
        .send()
        .await
        .unwrap()
        .json()
        .await
        .unwrap();

    let id = created["id"].as_i64().unwrap();
    let res = client
        .delete(format!("{url}/todos/{id}"))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), StatusCode::NO_CONTENT);

    let res = client
        .get(format!("{url}/todos/{id}"))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), StatusCode::NOT_FOUND);
}

#[tokio::test]
async fn test_delete_todo_not_found() {
    let (client, url) = spawn_server().await;

    let res = client
        .delete(format!("{url}/todos/99999"))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), StatusCode::NOT_FOUND);
}

#[tokio::test]
async fn test_full_lifecycle() {
    let (client, url) = spawn_server().await;

    // Luo
    let created: serde_json::Value = client
        .post(format!("{url}/todos"))
        .json(&serde_json::json!({
            "title": "Elinkaaritesti",
            "description": "Testataan koko CRUD-kierto",
            "due_date": "2026-12-31",
            "priority": 3,
            "status": "in_progress"
        }))
        .send()
        .await
        .unwrap()
        .json()
        .await
        .unwrap();

    let id = created["id"].as_i64().unwrap();
    assert_eq!(created["title"], "Elinkaaritesti");
    assert_eq!(created["description"], "Testataan koko CRUD-kierto");
    assert_eq!(created["due_date"], "2026-12-31");
    assert_eq!(created["priority"], 3);
    assert_eq!(created["status"], "in_progress");

    // Päivitä
    let updated: serde_json::Value = client
        .put(format!("{url}/todos/{id}"))
        .json(&serde_json::json!({"status": "done"}))
        .send()
        .await
        .unwrap()
        .json()
        .await
        .unwrap();

    assert_eq!(updated["status"], "done");
    assert_eq!(updated["title"], "Elinkaaritesti");

    // Poista
    let res = client
        .delete(format!("{url}/todos/{id}"))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), StatusCode::NO_CONTENT);
}
