mod common;
mod helpers;
use common::*;
use helpers::*;
use reqwest::StatusCode;
use serde_json::Value;

#[tokio::test]
async fn test_get_head_comprehensive() {
    let client = client();
    let (did, jwt) = setup_new_user("gethead").await;
    let res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getHead",
            base_url().await
        ))
        .query(&[("did", did.as_str())])
        .send()
        .await
        .expect("Failed to send request");
    assert_eq!(res.status(), StatusCode::OK);
    let body: Value = res.json().await.expect("Response was not valid JSON");
    assert!(body["root"].is_string());
    let root1 = body["root"].as_str().unwrap().to_string();
    assert!(root1.starts_with("bafy"), "Root CID should be a CID");
    let latest_res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getLatestCommit",
            base_url().await
        ))
        .query(&[("did", did.as_str())])
        .send()
        .await
        .expect("Failed to get latest commit");
    let latest_body: Value = latest_res.json().await.unwrap();
    let latest_cid = latest_body["cid"].as_str().unwrap();
    assert_eq!(
        root1, latest_cid,
        "getHead root should match getLatestCommit cid"
    );
    create_post(&client, &did, &jwt, "Post to change head").await;
    let res2 = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getHead",
            base_url().await
        ))
        .query(&[("did", did.as_str())])
        .send()
        .await
        .expect("Failed to get head after record");
    let body2: Value = res2.json().await.unwrap();
    let root2 = body2["root"].as_str().unwrap().to_string();
    assert_ne!(root1, root2, "Head CID should change after record creation");
    let not_found_res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getHead",
            base_url().await
        ))
        .query(&[("did", "did:plc:nonexistent12345")])
        .send()
        .await
        .expect("Failed to send request");
    assert_eq!(not_found_res.status(), StatusCode::BAD_REQUEST);
    let error_body: Value = not_found_res.json().await.unwrap();
    assert_eq!(error_body["error"], "RepoNotFound");
    let missing_res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getHead",
            base_url().await
        ))
        .send()
        .await
        .expect("Failed to send request");
    assert_eq!(missing_res.status(), StatusCode::BAD_REQUEST);
    let empty_res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getHead",
            base_url().await
        ))
        .query(&[("did", "")])
        .send()
        .await
        .expect("Failed to send request");
    assert_eq!(empty_res.status(), StatusCode::BAD_REQUEST);
    let whitespace_res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getHead",
            base_url().await
        ))
        .query(&[("did", "   ")])
        .send()
        .await
        .expect("Failed to send request");
    assert_eq!(whitespace_res.status(), StatusCode::BAD_REQUEST);
}

#[tokio::test]
async fn test_get_checkout_comprehensive() {
    let client = client();
    let (did, jwt) = setup_new_user("getcheckout").await;
    let empty_res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getCheckout",
            base_url().await
        ))
        .query(&[("did", did.as_str())])
        .send()
        .await
        .expect("Failed to send request");
    assert_eq!(empty_res.status(), StatusCode::OK);
    let empty_body = empty_res.bytes().await.expect("Failed to get body");
    assert!(
        !empty_body.is_empty(),
        "Even empty repo should return CAR header"
    );
    create_post(&client, &did, &jwt, "Post for checkout test").await;
    let res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getCheckout",
            base_url().await
        ))
        .query(&[("did", did.as_str())])
        .send()
        .await
        .expect("Failed to send request");
    assert_eq!(res.status(), StatusCode::OK);
    assert_eq!(
        res.headers()
            .get("content-type")
            .and_then(|h| h.to_str().ok()),
        Some("application/vnd.ipld.car")
    );
    let body = res.bytes().await.expect("Failed to get body");
    assert!(!body.is_empty(), "CAR file should not be empty");
    assert!(body.len() > 50, "CAR file should contain actual data");
    assert!(
        body.len() >= 2,
        "CAR file should have at least header length"
    );
    for i in 0..4 {
        tokio::time::sleep(std::time::Duration::from_millis(100)).await;
        create_post(&client, &did, &jwt, &format!("Checkout post {}", i)).await;
    }
    let multi_res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getCheckout",
            base_url().await
        ))
        .query(&[("did", did.as_str())])
        .send()
        .await
        .expect("Failed to send request");
    assert_eq!(multi_res.status(), StatusCode::OK);
    let multi_body = multi_res.bytes().await.expect("Failed to get body");
    assert!(
        multi_body.len() > 500,
        "CAR file with 5 records should be larger"
    );
    let not_found_res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getCheckout",
            base_url().await
        ))
        .query(&[("did", "did:plc:nonexistent12345")])
        .send()
        .await
        .expect("Failed to send request");
    assert_eq!(not_found_res.status(), StatusCode::BAD_REQUEST);
    let error_body: Value = not_found_res.json().await.unwrap();
    assert_eq!(error_body["error"], "RepoNotFound");
    let missing_res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getCheckout",
            base_url().await
        ))
        .send()
        .await
        .expect("Failed to send request");
    assert_eq!(missing_res.status(), StatusCode::BAD_REQUEST);
    let empty_did_res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getCheckout",
            base_url().await
        ))
        .query(&[("did", "")])
        .send()
        .await
        .expect("Failed to send request");
    assert_eq!(empty_did_res.status(), StatusCode::BAD_REQUEST);
}

#[tokio::test]
async fn test_get_head_deactivated_account_returns_error() {
    let client = client();
    let base = base_url().await;
    let (did, jwt) = setup_new_user("deactheadtest").await;
    let res = client
        .get(format!("{}/xrpc/com.atproto.sync.getHead", base))
        .query(&[("did", did.as_str())])
        .send()
        .await
        .unwrap();
    assert_eq!(res.status(), StatusCode::OK);
    client
        .post(format!(
            "{}/xrpc/com.atproto.server.deactivateAccount",
            base
        ))
        .bearer_auth(&jwt)
        .json(&serde_json::json!({}))
        .send()
        .await
        .unwrap();
    let deact_res = client
        .get(format!("{}/xrpc/com.atproto.sync.getHead", base))
        .query(&[("did", did.as_str())])
        .send()
        .await
        .unwrap();
    assert_eq!(deact_res.status(), StatusCode::BAD_REQUEST);
    let body: Value = deact_res.json().await.unwrap();
    assert_eq!(body["error"], "RepoDeactivated");
}

#[tokio::test]
async fn test_get_head_takendown_account_returns_error() {
    let client = client();
    let base = base_url().await;
    let (admin_jwt, _) = create_admin_account_and_login(&client).await;
    let (_, target_did) = create_account_and_login(&client).await;
    let res = client
        .get(format!("{}/xrpc/com.atproto.sync.getHead", base))
        .query(&[("did", target_did.as_str())])
        .send()
        .await
        .unwrap();
    assert_eq!(res.status(), StatusCode::OK);
    client
        .post(format!(
            "{}/xrpc/com.atproto.admin.updateSubjectStatus",
            base
        ))
        .bearer_auth(&admin_jwt)
        .json(&serde_json::json!({
            "subject": {
                "$type": "com.atproto.admin.defs#repoRef",
                "did": target_did
            },
            "takedown": {
                "applied": true,
                "ref": "test-takedown"
            }
        }))
        .send()
        .await
        .unwrap();
    let takedown_res = client
        .get(format!("{}/xrpc/com.atproto.sync.getHead", base))
        .query(&[("did", target_did.as_str())])
        .send()
        .await
        .unwrap();
    assert_eq!(takedown_res.status(), StatusCode::BAD_REQUEST);
    let body: Value = takedown_res.json().await.unwrap();
    assert_eq!(body["error"], "RepoTakendown");
}

#[tokio::test]
async fn test_get_head_admin_can_access_deactivated() {
    let client = client();
    let base = base_url().await;
    let (admin_jwt, _) = create_admin_account_and_login(&client).await;
    let (user_jwt, did) = create_account_and_login(&client).await;
    client
        .post(format!(
            "{}/xrpc/com.atproto.server.deactivateAccount",
            base
        ))
        .bearer_auth(&user_jwt)
        .json(&serde_json::json!({}))
        .send()
        .await
        .unwrap();
    let res = client
        .get(format!("{}/xrpc/com.atproto.sync.getHead", base))
        .bearer_auth(&admin_jwt)
        .query(&[("did", did.as_str())])
        .send()
        .await
        .unwrap();
    assert_eq!(res.status(), StatusCode::OK);
}

#[tokio::test]
async fn test_get_checkout_deactivated_account_returns_error() {
    let client = client();
    let base = base_url().await;
    let (did, jwt) = setup_new_user("deactcheckouttest").await;
    let res = client
        .get(format!("{}/xrpc/com.atproto.sync.getCheckout", base))
        .query(&[("did", did.as_str())])
        .send()
        .await
        .unwrap();
    assert_eq!(res.status(), StatusCode::OK);
    client
        .post(format!(
            "{}/xrpc/com.atproto.server.deactivateAccount",
            base
        ))
        .bearer_auth(&jwt)
        .json(&serde_json::json!({}))
        .send()
        .await
        .unwrap();
    let deact_res = client
        .get(format!("{}/xrpc/com.atproto.sync.getCheckout", base))
        .query(&[("did", did.as_str())])
        .send()
        .await
        .unwrap();
    assert_eq!(deact_res.status(), StatusCode::BAD_REQUEST);
    let body: Value = deact_res.json().await.unwrap();
    assert_eq!(body["error"], "RepoDeactivated");
}

#[tokio::test]
async fn test_get_checkout_takendown_account_returns_error() {
    let client = client();
    let base = base_url().await;
    let (admin_jwt, _) = create_admin_account_and_login(&client).await;
    let (_, target_did) = create_account_and_login(&client).await;
    let res = client
        .get(format!("{}/xrpc/com.atproto.sync.getCheckout", base))
        .query(&[("did", target_did.as_str())])
        .send()
        .await
        .unwrap();
    assert_eq!(res.status(), StatusCode::OK);
    client
        .post(format!(
            "{}/xrpc/com.atproto.admin.updateSubjectStatus",
            base
        ))
        .bearer_auth(&admin_jwt)
        .json(&serde_json::json!({
            "subject": {
                "$type": "com.atproto.admin.defs#repoRef",
                "did": target_did
            },
            "takedown": {
                "applied": true,
                "ref": "test-takedown"
            }
        }))
        .send()
        .await
        .unwrap();
    let takedown_res = client
        .get(format!("{}/xrpc/com.atproto.sync.getCheckout", base))
        .query(&[("did", target_did.as_str())])
        .send()
        .await
        .unwrap();
    assert_eq!(takedown_res.status(), StatusCode::BAD_REQUEST);
    let body: Value = takedown_res.json().await.unwrap();
    assert_eq!(body["error"], "RepoTakendown");
}
