use serde_json::json;
use sqlx::{Pool, Postgres};

use windmill_test_utils::*;

fn user_url(port: u16, endpoint: &str, name: &str) -> String {
    format!("http://localhost:{port}/api/w/test-workspace/users/{endpoint}/{name}")
}

fn client() -> reqwest::Client {
    reqwest::Client::new()
}

fn authed(builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
    builder.header("Authorization", "Bearer SECRET_TOKEN")
}

#[sqlx::test(migrations = "../migrations", fixtures("base"))]
async fn test_user_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
    initialize_tracing().await;
    let server = ApiServer::start(db.clone()).await?;
    let port = server.addr.port();
    let base = format!("http://localhost:{port}/api/w/test-workspace/users");

    // ===== Global (non-workspace) endpoints =====
    let global_base = format!("http://localhost:{port}/api/users");

    // --- global whoami ---
    let resp = authed(client().get(format!("{global_base}/whoami")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    let body = resp.json::<serde_json::Value>().await?;
    assert_eq!(body["email"], "test@windmill.dev");

    // --- get_email ---
    let resp = authed(client().get(format!("{global_base}/email")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    let email = resp.text().await?;
    assert_eq!(email, "test@windmill.dev");

    // --- exists_email ---
    let resp = authed(client().get(format!("{global_base}/exists/test@windmill.dev")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    assert_eq!(resp.json::<bool>().await?, true);

    let resp = authed(client().get(format!("{global_base}/exists/nonexistent@windmill.dev")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    assert_eq!(resp.json::<bool>().await?, false);

    // --- list_as_super_admin ---
    let resp = authed(client().get(format!("{global_base}/list_as_super_admin")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    let list = resp.json::<Vec<serde_json::Value>>().await?;
    assert!(!list.is_empty());

    // --- tokens/list ---
    let resp = authed(client().get(format!("{global_base}/tokens/list")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    resp.json::<Vec<serde_json::Value>>().await?;

    // --- tokens/create ---
    let resp = authed(client().post(format!("{global_base}/tokens/create")))
        .json(&json!({"label": "ephemeral-test-token"}))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 201);
    let new_token = resp.text().await?;
    assert!(!new_token.is_empty());

    let token_prefix = &new_token[..std::cmp::min(new_token.len(), 10)];

    // --- tokens/update_scopes (set explicit scopes) ---
    let resp = authed(client().post(format!("{global_base}/tokens/update_scopes/{token_prefix}")))
        .json(&json!({"scopes": ["jobs:run:scripts"]}))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200, "update_scopes: {}", resp.text().await?);

    // Verify via tokens/list that scopes were applied.
    let resp = authed(client().get(format!("{global_base}/tokens/list")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    let tokens = resp.json::<Vec<serde_json::Value>>().await?;
    let updated = tokens
        .iter()
        .find(|t| t["token_prefix"] == *token_prefix)
        .expect("token in list");
    assert_eq!(updated["scopes"], json!(["jobs:run:scripts"]));

    // --- tokens/update_scopes (clear scopes via null = full access) ---
    let resp = authed(client().post(format!("{global_base}/tokens/update_scopes/{token_prefix}")))
        .json(&json!({"scopes": null}))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);

    // --- tokens/update_scopes on nonexistent prefix returns 404 ---
    let resp = authed(client().post(format!("{global_base}/tokens/update_scopes/zzznotreal")))
        .json(&json!({"scopes": ["jobs:run:scripts"]}))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 404);

    // --- tokens/delete ---
    let resp = authed(client().delete(format!("{global_base}/tokens/delete/{token_prefix}")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200, "delete token: {}", resp.text().await?);

    // --- list_invites ---
    let resp = authed(client().get(format!("{global_base}/list_invites")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    resp.json::<Vec<serde_json::Value>>().await?;

    // --- username_info ---
    let resp = authed(client().get(format!("{global_base}/username_info/test@windmill.dev")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    let body = resp.json::<serde_json::Value>().await?;
    assert_eq!(body["username"], "test-user");

    // --- global usage ---
    let resp = authed(client().get(format!("{global_base}/usage")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);

    // --- tutorial_progress (get, then set, then get again) ---
    let resp = authed(client().get(format!("{global_base}/tutorial_progress")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    resp.json::<serde_json::Value>().await?;

    let resp = authed(client().post(format!("{global_base}/tutorial_progress")))
        .json(&json!({"progress": 42, "skipped_all": false}))
        .send()
        .await
        .unwrap();
    assert_eq!(
        resp.status(),
        200,
        "set tutorial_progress: {}",
        resp.text().await?
    );

    let resp = authed(client().get(format!("{global_base}/tutorial_progress")))
        .send()
        .await
        .unwrap();
    let body = resp.json::<serde_json::Value>().await?;
    assert_eq!(body["progress"], 42);

    // --- global update user ---
    let resp = authed(client().post(format!("{global_base}/update/test2@windmill.dev")))
        .json(&json!({"name": "Updated Test User 2"}))
        .send()
        .await
        .unwrap();
    assert_eq!(
        resp.status(),
        200,
        "global update user: {}",
        resp.text().await?
    );

    // --- setpassword (EE-gated in OSS) ---
    let resp = authed(client().post(format!("{global_base}/setpassword")))
        .json(&json!({"password": "new-test-password-123"}))
        .send()
        .await
        .unwrap();
    assert!(
        resp.status() == 200 || resp.status() == 500,
        "setpassword: unexpected status {}",
        resp.status()
    );

    // --- all_runnables ---
    let resp = authed(client().get(format!("{global_base}/all_runnables")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    resp.json::<Vec<serde_json::Value>>().await?;

    // --- onboarding (EE-gated in OSS) ---
    let resp = authed(client().post(format!("{global_base}/onboarding")))
        .json(&json!({"touch_point": "test", "use_case": "testing"}))
        .send()
        .await
        .unwrap();
    assert!(
        resp.status() == 200 || resp.status() == 500,
        "onboarding: unexpected status {}",
        resp.status()
    );

    // --- decline_invite (no pending invite, but endpoint should handle gracefully) ---
    let resp = authed(client().post(format!("{global_base}/decline_invite")))
        .json(&json!({"workspace_id": "nonexistent-ws"}))
        .send()
        .await
        .unwrap();
    assert!(
        resp.status() == 200 || resp.status() == 404,
        "decline_invite: unexpected status {}",
        resp.status()
    );

    // --- auth: is_first_time_setup (unauthed) ---
    let resp = client()
        .get(format!(
            "http://localhost:{port}/api/auth/is_first_time_setup"
        ))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    let is_first = resp.json::<bool>().await?;
    assert_eq!(is_first, false);

    // --- auth: is_smtp_configured (unauthed) ---
    let resp = client()
        .get(format!(
            "http://localhost:{port}/api/auth/is_smtp_configured"
        ))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    resp.json::<bool>().await?;

    // --- create user (global, EE-gated in OSS) ---
    let resp = authed(client().post(format!("{global_base}/create")))
        .json(&json!({
            "email": "newglobaluser@windmill.dev",
            "password": "test-password-123",
            "super_admin": false,
            "name": "New Global User"
        }))
        .send()
        .await
        .unwrap();
    let create_status = resp.status();
    assert!(
        create_status == 201 || create_status == 500,
        "create user: unexpected status {}",
        create_status
    );

    if create_status == 201 {
        // --- rename user (only if create succeeded / EE) ---
        let resp =
            authed(client().post(format!("{global_base}/rename/newglobaluser@windmill.dev")))
                .json(&json!({"new_username": "renamed_user"}))
                .send()
                .await
                .unwrap();
        assert_eq!(resp.status(), 200, "rename user: {}", resp.text().await?);

        // --- global delete user ---
        let resp =
            authed(client().delete(format!("{global_base}/delete/newglobaluser@windmill.dev")))
                .send()
                .await
                .unwrap();
        assert_eq!(
            resp.status(),
            200,
            "global delete user: {}",
            resp.text().await?
        );
    }

    // ===== Auth (unauthed) endpoints =====
    let auth_base = format!("http://localhost:{port}/api/auth");

    // --- login (will fail: password hash in fixture is fake) ---
    let resp = client()
        .post(format!("{auth_base}/login"))
        .json(&json!({"email": "test@windmill.dev", "password": "wrong-password"}))
        .send()
        .await
        .unwrap();
    assert!(
        resp.status() == 400 || resp.status() == 401 || resp.status() == 500,
        "login: unexpected status {}",
        resp.status()
    );

    // --- logout (POST, with auth token) ---
    let resp = authed(client().post(format!("{auth_base}/logout")))
        .send()
        .await
        .unwrap();
    assert!(
        resp.status() == 200 || resp.status() == 303,
        "logout POST: unexpected status {}",
        resp.status()
    );

    // --- logout (GET, with auth token) ---
    let resp = authed(client().get(format!("{auth_base}/logout")))
        .send()
        .await
        .unwrap();
    assert!(
        resp.status() == 200 || resp.status() == 303,
        "logout GET: unexpected status {}",
        resp.status()
    );

    // --- request_password_reset (returns 400 if SMTP not configured) ---
    let resp = client()
        .post(format!("{auth_base}/request_password_reset"))
        .json(&json!({"email": "test@windmill.dev"}))
        .send()
        .await
        .unwrap();
    assert!(
        resp.status() == 200 || resp.status() == 400,
        "request_password_reset: unexpected status {}",
        resp.status()
    );

    // --- reset_password (EE-gated, invalid token) ---
    let resp = client()
        .post(format!("{auth_base}/reset_password"))
        .json(&json!({"token": "invalid-token", "new_password": "new-pass"}))
        .send()
        .await
        .unwrap();
    assert!(
        resp.status() == 400 || resp.status() == 500,
        "reset_password: unexpected status {}",
        resp.status()
    );

    // ===== Workspace-scoped endpoints =====

    // --- whoami ---
    let resp = authed(client().get(format!("{base}/whoami")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    let body = resp.json::<serde_json::Value>().await?;
    assert_eq!(body["username"], "test-user");
    assert_eq!(body["email"], "test@windmill.dev");
    assert_eq!(body["is_admin"], true);

    // --- list ---
    let resp = authed(client().get(format!("{base}/list")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    let list = resp.json::<Vec<serde_json::Value>>().await?;
    assert!(!list.is_empty());
    assert!(list.iter().any(|u| u["username"] == "test-user"));

    // --- list_usernames ---
    let resp = authed(client().get(format!("{base}/list_usernames")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    let usernames = resp.json::<Vec<String>>().await?;
    assert!(usernames.contains(&"test-user".to_string()));

    // --- get ---
    let resp = authed(client().get(user_url(port, "get", "test-user")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    let body = resp.json::<serde_json::Value>().await?;
    assert_eq!(body["username"], "test-user");

    // --- whois ---
    let resp = authed(client().get(user_url(port, "whois", "test-user")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    let body = resp.json::<serde_json::Value>().await?;
    assert_eq!(body["username"], "test-user");

    // --- username_to_email ---
    let resp = authed(client().get(user_url(port, "username_to_email", "test-user")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    let email = resp.text().await?;
    assert_eq!(email, "test@windmill.dev");

    // --- exists ---
    let resp = authed(client().post(format!("{base}/exists")))
        .json(&json!({"username": "test-user"}))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    assert_eq!(resp.json::<bool>().await?, true);

    let resp = authed(client().post(format!("{base}/exists")))
        .json(&json!({"username": "nonexistent"}))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    assert_eq!(resp.json::<bool>().await?, false);

    // --- list_usage ---
    let resp = authed(client().get(format!("{base}/list_usage")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    resp.json::<Vec<serde_json::Value>>().await?;

    // --- is_owner ---
    let resp = authed(client().get(format!(
        "http://localhost:{port}/api/w/test-workspace/users/is_owner/u/test-user/test"
    )))
    .send()
    .await
    .unwrap();
    assert_eq!(resp.status(), 200);

    // --- update (make non-admin, then revert) ---
    let resp = authed(client().post(user_url(port, "update", "test-user")))
        .json(&json!({"is_admin": false}))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200, "update user: {}", resp.text().await?);

    // revert back to admin
    let resp = authed(client().post(user_url(port, "update", "test-user")))
        .json(&json!({"is_admin": true}))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);

    // --- delete workspace user (admin deletes test-user-2) ---
    let resp = authed(client().delete(user_url(port, "delete", "test-user-2")))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200, "delete user: {}", resp.text().await?);

    // verify deleted
    let resp = authed(client().get(format!("{base}/list_usernames")))
        .send()
        .await
        .unwrap();
    let usernames = resp.json::<Vec<String>>().await?;
    assert!(!usernames.contains(&"test-user-2".to_string()));

    // --- leave workspace (test-user-3 leaves voluntarily) ---
    let resp = client()
        .post(format!("{base}/leave"))
        .header("Authorization", "Bearer SECRET_TOKEN_3")
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200, "leave: {}", resp.text().await?);

    // verify left
    let resp = authed(client().get(format!("{base}/list_usernames")))
        .send()
        .await
        .unwrap();
    let usernames = resp.json::<Vec<String>>().await?;
    assert!(!usernames.contains(&"test-user-3".to_string()));

    Ok(())
}
