mod common;
mod helpers;
use chrono::Utc;
use common::*;
use helpers::*;
use reqwest::StatusCode;
use serde_json::{Value, json};

#[tokio::test]
async fn test_session_lifecycle_wrong_password() {
    let client = client();
    let (_, _) = setup_new_user("session-wrong-pw").await;
    let login_payload = json!({
        "identifier": format!("session-wrong-pw-{}.test", Utc::now().timestamp_millis()),
        "password": "wrong-password"
    });
    let res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createSession",
            base_url().await
        ))
        .json(&login_payload)
        .send()
        .await
        .expect("Failed to send request");
    assert!(
        res.status() == StatusCode::UNAUTHORIZED || res.status() == StatusCode::BAD_REQUEST,
        "Expected 401 or 400 for wrong password, got {}",
        res.status()
    );
}

#[tokio::test]
async fn test_session_lifecycle_multiple_sessions() {
    let client = client();
    let ts = Utc::now().timestamp_millis();
    let handle = format!("multi-session-{}.test", ts);
    let email = format!("multi-session-{}@test.com", ts);
    let password = "Multisession123!";
    let create_payload = json!({
        "handle": handle,
        "email": email,
        "password": password
    });
    let create_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createAccount",
            base_url().await
        ))
        .json(&create_payload)
        .send()
        .await
        .expect("Failed to create account");
    assert_eq!(create_res.status(), StatusCode::OK);
    let create_body: Value = create_res.json().await.unwrap();
    let did = create_body["did"].as_str().unwrap();
    let _ = verify_new_account(&client, did).await;
    let login_payload = json!({
        "identifier": handle,
        "password": password
    });
    let session1_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createSession",
            base_url().await
        ))
        .json(&login_payload)
        .send()
        .await
        .expect("Failed session 1");
    assert_eq!(session1_res.status(), StatusCode::OK);
    let session1: Value = session1_res.json().await.unwrap();
    let jwt1 = session1["accessJwt"].as_str().unwrap();
    let session2_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createSession",
            base_url().await
        ))
        .json(&login_payload)
        .send()
        .await
        .expect("Failed session 2");
    assert_eq!(session2_res.status(), StatusCode::OK);
    let session2: Value = session2_res.json().await.unwrap();
    let jwt2 = session2["accessJwt"].as_str().unwrap();
    assert_ne!(jwt1, jwt2, "Sessions should have different tokens");
    let get1 = client
        .get(format!(
            "{}/xrpc/com.atproto.server.getSession",
            base_url().await
        ))
        .bearer_auth(jwt1)
        .send()
        .await
        .expect("Failed getSession 1");
    assert_eq!(get1.status(), StatusCode::OK);
    let get2 = client
        .get(format!(
            "{}/xrpc/com.atproto.server.getSession",
            base_url().await
        ))
        .bearer_auth(jwt2)
        .send()
        .await
        .expect("Failed getSession 2");
    assert_eq!(get2.status(), StatusCode::OK);
}

#[tokio::test]
async fn test_session_lifecycle_refresh_invalidates_old() {
    let client = client();
    let ts = Utc::now().timestamp_millis();
    let handle = format!("refresh-inv-{}.test", ts);
    let email = format!("refresh-inv-{}@test.com", ts);
    let password = "Refresh123inv!";
    let create_payload = json!({
        "handle": handle,
        "email": email,
        "password": password
    });
    let create_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createAccount",
            base_url().await
        ))
        .json(&create_payload)
        .send()
        .await
        .expect("Failed to create account");
    let create_body: Value = create_res.json().await.unwrap();
    let did = create_body["did"].as_str().unwrap();
    let _ = verify_new_account(&client, did).await;
    let login_payload = json!({
        "identifier": handle,
        "password": password
    });
    let login_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createSession",
            base_url().await
        ))
        .json(&login_payload)
        .send()
        .await
        .expect("Failed login");
    let login_body: Value = login_res.json().await.unwrap();
    let refresh_jwt = login_body["refreshJwt"].as_str().unwrap().to_string();
    let refresh_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.refreshSession",
            base_url().await
        ))
        .bearer_auth(&refresh_jwt)
        .send()
        .await
        .expect("Failed first refresh");
    assert_eq!(refresh_res.status(), StatusCode::OK);
    let refresh_body: Value = refresh_res.json().await.unwrap();
    let new_refresh_jwt = refresh_body["refreshJwt"].as_str().unwrap();
    assert_ne!(refresh_jwt, new_refresh_jwt, "Refresh tokens should differ");
    let reuse_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.refreshSession",
            base_url().await
        ))
        .bearer_auth(&refresh_jwt)
        .send()
        .await
        .expect("Failed reuse attempt");
    assert!(
        reuse_res.status() == StatusCode::UNAUTHORIZED
            || reuse_res.status() == StatusCode::BAD_REQUEST,
        "Old refresh token should be invalid after use"
    );
}

#[tokio::test]
async fn test_app_password_lifecycle() {
    let client = client();
    let ts = Utc::now().timestamp_millis();
    let handle = format!("apppass-{}.test", ts);
    let email = format!("apppass-{}@test.com", ts);
    let password = "Apppass123!";
    let create_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createAccount",
            base_url().await
        ))
        .json(&json!({
            "handle": handle,
            "email": email,
            "password": password
        }))
        .send()
        .await
        .expect("Failed to create account");
    assert_eq!(create_res.status(), StatusCode::OK);
    let account: Value = create_res.json().await.unwrap();
    let did = account["did"].as_str().unwrap();
    let jwt = verify_new_account(&client, did).await;
    let create_app_pass_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createAppPassword",
            base_url().await
        ))
        .bearer_auth(&jwt)
        .json(&json!({ "name": "Test App" }))
        .send()
        .await
        .expect("Failed to create app password");
    assert_eq!(create_app_pass_res.status(), StatusCode::OK);
    let app_pass: Value = create_app_pass_res.json().await.unwrap();
    let app_password = app_pass["password"].as_str().unwrap().to_string();
    assert_eq!(app_pass["name"], "Test App");
    let list_res = client
        .get(format!(
            "{}/xrpc/com.atproto.server.listAppPasswords",
            base_url().await
        ))
        .bearer_auth(&jwt)
        .send()
        .await
        .expect("Failed to list app passwords");
    assert_eq!(list_res.status(), StatusCode::OK);
    let list_body: Value = list_res.json().await.unwrap();
    let passwords = list_body["passwords"].as_array().unwrap();
    assert_eq!(passwords.len(), 1);
    assert_eq!(passwords[0]["name"], "Test App");
    let login_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createSession",
            base_url().await
        ))
        .json(&json!({
            "identifier": handle,
            "password": app_password
        }))
        .send()
        .await
        .expect("Failed to login with app password");
    assert_eq!(
        login_res.status(),
        StatusCode::OK,
        "App password login should work"
    );
    let revoke_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.revokeAppPassword",
            base_url().await
        ))
        .bearer_auth(&jwt)
        .json(&json!({ "name": "Test App" }))
        .send()
        .await
        .expect("Failed to revoke app password");
    assert_eq!(revoke_res.status(), StatusCode::OK);
    let login_after_revoke = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createSession",
            base_url().await
        ))
        .json(&json!({
            "identifier": handle,
            "password": app_password
        }))
        .send()
        .await
        .expect("Failed to attempt login after revoke");
    assert!(
        login_after_revoke.status() == StatusCode::UNAUTHORIZED
            || login_after_revoke.status() == StatusCode::BAD_REQUEST,
        "Revoked app password should not work"
    );
    let list_after_revoke = client
        .get(format!(
            "{}/xrpc/com.atproto.server.listAppPasswords",
            base_url().await
        ))
        .bearer_auth(&jwt)
        .send()
        .await
        .expect("Failed to list after revoke");
    let list_after: Value = list_after_revoke.json().await.unwrap();
    let passwords_after = list_after["passwords"].as_array().unwrap();
    assert_eq!(passwords_after.len(), 0, "No app passwords should remain");
}

#[tokio::test]
async fn test_app_password_duplicate_name() {
    let client = client();
    let base = base_url().await;
    let (jwt, _did) = create_account_and_login(&client).await;
    let create_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createAppPassword",
            base
        ))
        .bearer_auth(&jwt)
        .json(&json!({ "name": "My App" }))
        .send()
        .await
        .expect("Failed to create app password");
    assert_eq!(create_res.status(), StatusCode::OK);
    let duplicate_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createAppPassword",
            base
        ))
        .bearer_auth(&jwt)
        .json(&json!({ "name": "My App" }))
        .send()
        .await
        .expect("Failed to attempt duplicate");
    assert_eq!(
        duplicate_res.status(),
        StatusCode::BAD_REQUEST,
        "Duplicate app password name should fail"
    );
    let body: Value = duplicate_res.json().await.unwrap();
    assert_eq!(body["error"], "DuplicateAppPassword");
}

#[tokio::test]
async fn test_app_password_revoke_nonexistent() {
    let client = client();
    let base = base_url().await;
    let (jwt, _did) = create_account_and_login(&client).await;
    let revoke_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.revokeAppPassword",
            base
        ))
        .bearer_auth(&jwt)
        .json(&json!({ "name": "Does Not Exist" }))
        .send()
        .await
        .expect("Failed to revoke");
    assert_eq!(
        revoke_res.status(),
        StatusCode::OK,
        "Revoking non-existent app password should succeed silently"
    );
}

#[tokio::test]
async fn test_app_password_revoke_invalidates_sessions() {
    let client = client();
    let base = base_url().await;
    let ts = Utc::now().timestamp_millis();
    let handle = format!("apppass-inv-{}.test", ts);
    let email = format!("apppass-inv-{}@test.com", ts);
    let password = "ApppassInv123!";
    let create_res = client
        .post(format!("{}/xrpc/com.atproto.server.createAccount", base))
        .json(&json!({
            "handle": handle,
            "email": email,
            "password": password
        }))
        .send()
        .await
        .expect("Failed to create account");
    assert_eq!(create_res.status(), StatusCode::OK);
    let account: Value = create_res.json().await.unwrap();
    let did = account["did"].as_str().unwrap();
    let main_jwt = verify_new_account(&client, did).await;
    let create_app_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createAppPassword",
            base
        ))
        .bearer_auth(&main_jwt)
        .json(&json!({ "name": "Session Test App" }))
        .send()
        .await
        .expect("Failed to create app password");
    assert_eq!(create_app_res.status(), StatusCode::OK);
    let app_pass: Value = create_app_res.json().await.unwrap();
    let app_password = app_pass["password"].as_str().unwrap();
    let app_session_res = client
        .post(format!("{}/xrpc/com.atproto.server.createSession", base))
        .json(&json!({
            "identifier": handle,
            "password": app_password
        }))
        .send()
        .await
        .expect("Failed to login with app password");
    assert_eq!(app_session_res.status(), StatusCode::OK);
    let app_session: Value = app_session_res.json().await.unwrap();
    let app_jwt = app_session["accessJwt"].as_str().unwrap();
    let get_session_res = client
        .get(format!("{}/xrpc/com.atproto.server.getSession", base))
        .bearer_auth(app_jwt)
        .send()
        .await
        .expect("Failed to get session");
    assert_eq!(
        get_session_res.status(),
        StatusCode::OK,
        "App password session should be valid before revocation"
    );
    let revoke_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.revokeAppPassword",
            base
        ))
        .bearer_auth(&main_jwt)
        .json(&json!({ "name": "Session Test App" }))
        .send()
        .await
        .expect("Failed to revoke app password");
    assert_eq!(revoke_res.status(), StatusCode::OK);
    let get_session_after = client
        .get(format!("{}/xrpc/com.atproto.server.getSession", base))
        .bearer_auth(app_jwt)
        .send()
        .await
        .expect("Failed to check session after revoke");
    assert!(
        get_session_after.status() == StatusCode::UNAUTHORIZED
            || get_session_after.status() == StatusCode::BAD_REQUEST,
        "Session created with revoked app password should be invalid, got {}",
        get_session_after.status()
    );
    let main_session_res = client
        .get(format!("{}/xrpc/com.atproto.server.getSession", base))
        .bearer_auth(&main_jwt)
        .send()
        .await
        .expect("Failed to check main session");
    assert_eq!(
        main_session_res.status(),
        StatusCode::OK,
        "Main session should still be valid after revoking app password"
    );
}

#[tokio::test]
async fn test_account_deactivation_lifecycle() {
    let client = client();
    let ts = Utc::now().timestamp_millis();
    let handle = format!("deactivate-{}.test", ts);
    let email = format!("deactivate-{}@test.com", ts);
    let password = "Deactivate123!";
    let create_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createAccount",
            base_url().await
        ))
        .json(&json!({
            "handle": handle,
            "email": email,
            "password": password
        }))
        .send()
        .await
        .expect("Failed to create account");
    assert_eq!(create_res.status(), StatusCode::OK);
    let account: Value = create_res.json().await.unwrap();
    let did = account["did"].as_str().unwrap().to_string();
    let jwt = verify_new_account(&client, &did).await;
    let (post_uri, _) = create_post(&client, &did, &jwt, "Post before deactivation").await;
    let post_rkey = post_uri.split('/').next_back().unwrap();
    let status_before = client
        .get(format!(
            "{}/xrpc/com.atproto.server.checkAccountStatus",
            base_url().await
        ))
        .bearer_auth(&jwt)
        .send()
        .await
        .expect("Failed to check status");
    assert_eq!(status_before.status(), StatusCode::OK);
    let status_body: Value = status_before.json().await.unwrap();
    assert_eq!(status_body["activated"], true);
    let deactivate_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.deactivateAccount",
            base_url().await
        ))
        .bearer_auth(&jwt)
        .json(&json!({}))
        .send()
        .await
        .expect("Failed to deactivate");
    assert_eq!(deactivate_res.status(), StatusCode::OK);
    let get_post_res = client
        .get(format!(
            "{}/xrpc/com.atproto.repo.getRecord",
            base_url().await
        ))
        .query(&[
            ("repo", did.as_str()),
            ("collection", "app.bsky.feed.post"),
            ("rkey", post_rkey),
        ])
        .send()
        .await
        .expect("Failed to get post while deactivated");
    assert_eq!(
        get_post_res.status(),
        StatusCode::OK,
        "Records should still be readable"
    );
    let activate_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.activateAccount",
            base_url().await
        ))
        .bearer_auth(&jwt)
        .json(&json!({}))
        .send()
        .await
        .expect("Failed to reactivate");
    assert_eq!(activate_res.status(), StatusCode::OK);
    let status_after_activate = client
        .get(format!(
            "{}/xrpc/com.atproto.server.checkAccountStatus",
            base_url().await
        ))
        .bearer_auth(&jwt)
        .send()
        .await
        .expect("Failed to check status after activate");
    assert_eq!(status_after_activate.status(), StatusCode::OK);
    let (new_post_uri, _) = create_post(&client, &did, &jwt, "Post after reactivation").await;
    assert!(
        !new_post_uri.is_empty(),
        "Should be able to post after reactivation"
    );
}

#[tokio::test]
async fn test_service_auth_lifecycle() {
    let client = client();
    let (did, jwt) = setup_new_user("service-auth-test").await;
    let service_auth_res = client
        .get(format!(
            "{}/xrpc/com.atproto.server.getServiceAuth",
            base_url().await
        ))
        .query(&[
            ("aud", "did:web:api.bsky.app"),
            ("lxm", "com.atproto.repo.uploadBlob"),
        ])
        .bearer_auth(&jwt)
        .send()
        .await
        .expect("Failed to get service auth");
    assert_eq!(service_auth_res.status(), StatusCode::OK);
    let auth_body: Value = service_auth_res.json().await.unwrap();
    let service_token = auth_body["token"].as_str().expect("No token in response");
    let parts: Vec<&str> = service_token.split('.').collect();
    assert_eq!(parts.len(), 3, "Service token should be a valid JWT");
    use base64::Engine;
    let payload_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
        .decode(parts[1])
        .expect("Failed to decode JWT payload");
    let claims: Value = serde_json::from_slice(&payload_bytes).expect("Invalid JWT payload");
    assert_eq!(claims["iss"], did);
    assert_eq!(claims["aud"], "did:web:api.bsky.app");
    assert_eq!(claims["lxm"], "com.atproto.repo.uploadBlob");
}

#[tokio::test]
async fn test_request_account_delete() {
    let client = client();
    let (did, jwt) = setup_new_user("request-delete-test").await;
    let res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.requestAccountDelete",
            base_url().await
        ))
        .bearer_auth(&jwt)
        .send()
        .await
        .expect("Failed to request account deletion");
    assert_eq!(res.status(), StatusCode::OK);
    let repos = get_test_repos().await;
    let deletion_request = repos
        .infra
        .get_deletion_request_by_did(&tranquil_types::Did::new(did.clone()).unwrap())
        .await
        .expect("Failed to query DB");
    assert!(
        deletion_request.is_some(),
        "Deletion token should exist in DB"
    );
    let deletion_request = deletion_request.unwrap();
    assert!(
        !deletion_request.token.is_empty(),
        "Token should not be empty"
    );
    assert!(
        deletion_request.expires_at > Utc::now(),
        "Token should not be expired"
    );
}

async fn create_app_password_session(
    client: &reqwest::Client,
    did: &str,
    main_jwt: &str,
    name: &str,
    body: Value,
) -> (String, Value) {
    let base = base_url().await;
    let create_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createAppPassword",
            base
        ))
        .bearer_auth(main_jwt)
        .json(&body)
        .send()
        .await
        .expect("Failed to create app password");
    assert_eq!(create_res.status(), StatusCode::OK);
    let app_pass: Value = create_res.json().await.unwrap();
    let password = app_pass["password"].as_str().unwrap().to_string();
    let scopes_response = app_pass.clone();
    let login_res = client
        .post(format!("{}/xrpc/com.atproto.server.createSession", base))
        .json(&json!({ "identifier": did, "password": password }))
        .send()
        .await
        .expect("Failed to login with app password");
    assert_eq!(
        login_res.status(),
        StatusCode::OK,
        "App password login for '{}' failed",
        name
    );
    let session: Value = login_res.json().await.unwrap();
    let jwt = session["accessJwt"].as_str().unwrap().to_string();
    (jwt, scopes_response)
}

async fn try_chat_service_auth(client: &reqwest::Client, jwt: &str) -> StatusCode {
    let base = base_url().await;
    let res = client
        .get(format!("{}/xrpc/com.atproto.server.getServiceAuth", base))
        .bearer_auth(jwt)
        .query(&[
            ("aud", "did:web:api.bsky.app"),
            ("lxm", "chat.bsky.convo.listConvos"),
        ])
        .send()
        .await
        .expect("Failed to call getServiceAuth");
    res.status()
}

#[tokio::test]
async fn test_app_password_non_privileged_blocks_chat() {
    let client = client();
    let (did, jwt) = setup_new_user("appscope-nonchat").await;
    let (app_jwt, create_body) = create_app_password_session(
        &client,
        &did,
        &jwt,
        "non-privileged",
        json!({ "name": "NoChatApp", "privileged": false }),
    )
    .await;
    assert_eq!(
        create_body["scopes"].as_str().unwrap(),
        "transition:generic",
        "Non-privileged app password should not have chat scope"
    );
    let status = try_chat_service_auth(&client, &app_jwt).await;
    assert_eq!(
        status,
        StatusCode::FORBIDDEN,
        "Non-privileged app password must not access chat methods"
    );
}

#[tokio::test]
async fn test_app_password_privileged_allows_chat() {
    let client = client();
    let (did, jwt) = setup_new_user("appscope-chat").await;
    let (app_jwt, create_body) = create_app_password_session(
        &client,
        &did,
        &jwt,
        "privileged",
        json!({ "name": "ChatApp", "privileged": true }),
    )
    .await;
    assert_eq!(
        create_body["scopes"].as_str().unwrap(),
        "transition:generic transition:chat.bsky",
        "Privileged app password should have chat scope"
    );
    let status = try_chat_service_auth(&client, &app_jwt).await;
    assert_eq!(
        status,
        StatusCode::OK,
        "Privileged app password should access chat methods"
    );
}

#[tokio::test]
async fn test_app_password_no_privileged_field_allows_chat() {
    let client = client();
    let (did, jwt) = setup_new_user("appscope-full").await;
    let (app_jwt, create_body) = create_app_password_session(
        &client,
        &did,
        &jwt,
        "full-access",
        json!({ "name": "FullApp" }),
    )
    .await;
    assert_eq!(
        create_body["scopes"].as_str().unwrap(),
        "transition:generic transition:chat.bsky",
        "App password without privileged field should default to full access"
    );
    let status = try_chat_service_auth(&client, &app_jwt).await;
    assert_eq!(
        status,
        StatusCode::OK,
        "Full-access app password should access chat methods"
    );
}

#[tokio::test]
async fn test_app_password_explicit_scopes_respected() {
    let client = client();
    let (did, jwt) = setup_new_user("appscope-explicit").await;
    let (app_jwt, create_body) = create_app_password_session(
        &client,
        &did,
        &jwt,
        "explicit-scopes",
        json!({ "name": "ScopedApp", "scopes": "transition:generic" }),
    )
    .await;
    assert_eq!(
        create_body["scopes"].as_str().unwrap(),
        "transition:generic",
        "Explicit scopes should be stored as-is"
    );
    let status = try_chat_service_auth(&client, &app_jwt).await;
    assert_eq!(
        status,
        StatusCode::FORBIDDEN,
        "App password with only transition:generic should not access chat"
    );
}
