mod common;
use common::*;
use iroh_car::CarHeader;
use reqwest::StatusCode;
use serde_json::json;

#[tokio::test]
async fn test_import_repo_requires_auth() {
    let client = client();
    let res = client
        .post(format!(
            "{}/xrpc/com.atproto.repo.importRepo",
            base_url().await
        ))
        .header("Content-Type", "application/vnd.ipld.car")
        .body(vec![0u8; 100])
        .send()
        .await
        .expect("Request failed");
    assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
}

#[tokio::test]
async fn test_import_repo_invalid_car() {
    let client = client();
    let (token, _did) = create_account_and_login(&client).await;
    let res = client
        .post(format!(
            "{}/xrpc/com.atproto.repo.importRepo",
            base_url().await
        ))
        .bearer_auth(&token)
        .header("Content-Type", "application/vnd.ipld.car")
        .body(vec![0u8; 100])
        .send()
        .await
        .expect("Request failed");
    assert_eq!(res.status(), StatusCode::BAD_REQUEST);
    let body: serde_json::Value = res.json().await.unwrap();
    assert_eq!(body["error"], "InvalidRequest");
}

#[tokio::test]
async fn test_import_repo_empty_body() {
    let client = client();
    let (token, _did) = create_account_and_login(&client).await;
    let res = client
        .post(format!(
            "{}/xrpc/com.atproto.repo.importRepo",
            base_url().await
        ))
        .bearer_auth(&token)
        .header("Content-Type", "application/vnd.ipld.car")
        .body(vec![])
        .send()
        .await
        .expect("Request failed");
    assert_eq!(res.status(), StatusCode::BAD_REQUEST);
}

fn write_varint(buf: &mut Vec<u8>, mut value: u64) {
    loop {
        let mut byte = (value & 0x7F) as u8;
        value >>= 7;
        if value != 0 {
            byte |= 0x80;
        }
        buf.push(byte);
        if value == 0 {
            break;
        }
    }
}

#[tokio::test]
async fn test_import_rejects_car_for_different_user() {
    let client = client();
    let (token_a, _did_a) = create_account_and_login(&client).await;
    let (_token_b, did_b) = create_account_and_login(&client).await;
    let export_res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getRepo?did={}",
            base_url().await,
            did_b
        ))
        .send()
        .await
        .expect("Export failed");
    assert_eq!(export_res.status(), StatusCode::OK);
    let car_bytes = export_res.bytes().await.unwrap();
    let import_res = client
        .post(format!(
            "{}/xrpc/com.atproto.repo.importRepo",
            base_url().await
        ))
        .bearer_auth(&token_a)
        .header("Content-Type", "application/vnd.ipld.car")
        .body(car_bytes.to_vec())
        .send()
        .await
        .expect("Import failed");
    assert_eq!(import_res.status(), StatusCode::FORBIDDEN);
    let body: serde_json::Value = import_res.json().await.unwrap();
    assert!(
        body["error"] == "InvalidRepo"
            || body["error"] == "InvalidRequest"
            || body["error"] == "DidMismatch",
        "Expected InvalidRepo, DidMismatch, or InvalidRequest error, got: {:?}",
        body
    );
}

#[tokio::test]
async fn test_import_accepts_own_exported_repo() {
    let client = client();
    let (token, did) = create_account_and_login(&client).await;
    let post_payload = json!({
        "repo": did,
        "collection": "app.bsky.feed.post",
        "record": {
            "$type": "app.bsky.feed.post",
            "text": "Original post before export",
            "createdAt": chrono::Utc::now().to_rfc3339(),
        }
    });
    let create_res = client
        .post(format!(
            "{}/xrpc/com.atproto.repo.createRecord",
            base_url().await
        ))
        .bearer_auth(&token)
        .json(&post_payload)
        .send()
        .await
        .expect("Failed to create post");
    assert_eq!(create_res.status(), StatusCode::OK);
    let export_res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getRepo?did={}",
            base_url().await,
            did
        ))
        .send()
        .await
        .expect("Failed to export repo");
    assert_eq!(export_res.status(), StatusCode::OK);
    let car_bytes = export_res.bytes().await.unwrap();
    let import_res = client
        .post(format!(
            "{}/xrpc/com.atproto.repo.importRepo",
            base_url().await
        ))
        .bearer_auth(&token)
        .header("Content-Type", "application/vnd.ipld.car")
        .body(car_bytes.to_vec())
        .send()
        .await
        .expect("Failed to import repo");
    let status = import_res.status();
    if status != StatusCode::OK {
        let body = import_res.text().await.unwrap_or_default();
        panic!("Import failed with status {}: {}", status, body);
    }
}

#[tokio::test]
async fn test_import_repo_size_limit() {
    let client = client();
    let (token, _did) = create_account_and_login(&client).await;
    let oversized_body = vec![0u8; 110 * 1024 * 1024];
    let res = client
        .post(format!(
            "{}/xrpc/com.atproto.repo.importRepo",
            base_url().await
        ))
        .bearer_auth(&token)
        .header("Content-Type", "application/vnd.ipld.car")
        .body(oversized_body)
        .send()
        .await;
    match res {
        Ok(response) => {
            assert_eq!(response.status(), StatusCode::PAYLOAD_TOO_LARGE);
        }
        Err(e) => {
            let error_str = e.to_string().to_lowercase();
            assert!(
                error_str.contains("broken pipe")
                    || error_str.contains("connection")
                    || error_str.contains("reset")
                    || error_str.contains("request")
                    || error_str.contains("body"),
                "Expected connection error or PAYLOAD_TOO_LARGE, got: {}",
                e
            );
        }
    }
}

#[tokio::test]
async fn test_import_deactivated_account_allowed_for_migration() {
    let client = client();
    let (token, did) = create_account_and_login(&client).await;
    let export_res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getRepo?did={}",
            base_url().await,
            did
        ))
        .send()
        .await
        .expect("Export failed");
    assert_eq!(export_res.status(), StatusCode::OK);
    let car_bytes = export_res.bytes().await.unwrap();
    let deactivate_res = client
        .post(format!(
            "{}/xrpc/com.atproto.server.deactivateAccount",
            base_url().await
        ))
        .bearer_auth(&token)
        .json(&json!({}))
        .send()
        .await
        .expect("Deactivate failed");
    assert!(deactivate_res.status().is_success());
    let import_res = client
        .post(format!(
            "{}/xrpc/com.atproto.repo.importRepo",
            base_url().await
        ))
        .bearer_auth(&token)
        .header("Content-Type", "application/vnd.ipld.car")
        .body(car_bytes.to_vec())
        .send()
        .await
        .expect("Import failed");
    assert!(
        import_res.status().is_success(),
        "Deactivated accounts should allow import for migration, got {}",
        import_res.status()
    );
}

#[tokio::test]
async fn test_import_invalid_car_structure() {
    let client = client();
    let (token, _did) = create_account_and_login(&client).await;
    let invalid_car = vec![0x0a, 0xa1, 0x65, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x80];
    let res = client
        .post(format!(
            "{}/xrpc/com.atproto.repo.importRepo",
            base_url().await
        ))
        .bearer_auth(&token)
        .header("Content-Type", "application/vnd.ipld.car")
        .body(invalid_car)
        .send()
        .await
        .expect("Request failed");
    assert_eq!(res.status(), StatusCode::BAD_REQUEST);
}

#[tokio::test]
async fn test_import_car_with_no_roots() {
    let client = client();
    let (token, _did) = create_account_and_login(&client).await;
    let header = CarHeader::new_v1(vec![]);
    let header_cbor = header.encode().unwrap_or_default();
    let mut car = Vec::new();
    write_varint(&mut car, header_cbor.len() as u64);
    car.extend_from_slice(&header_cbor);
    let res = client
        .post(format!(
            "{}/xrpc/com.atproto.repo.importRepo",
            base_url().await
        ))
        .bearer_auth(&token)
        .header("Content-Type", "application/vnd.ipld.car")
        .body(car)
        .send()
        .await
        .expect("Request failed");
    assert_eq!(res.status(), StatusCode::BAD_REQUEST);
    let body: serde_json::Value = res.json().await.unwrap();
    assert_eq!(body["error"], "InvalidRequest");
}

#[tokio::test]
async fn test_import_preserves_records_after_reimport() {
    let client = client();
    let (token, did) = create_account_and_login(&client).await;
    let mut rkeys = Vec::with_capacity(3);
    for i in 0..3 {
        let post_payload = json!({
            "repo": did,
            "collection": "app.bsky.feed.post",
            "record": {
                "$type": "app.bsky.feed.post",
                "text": format!("Test post {}", i),
                "createdAt": chrono::Utc::now().to_rfc3339(),
            }
        });
        let res = client
            .post(format!(
                "{}/xrpc/com.atproto.repo.createRecord",
                base_url().await
            ))
            .bearer_auth(&token)
            .json(&post_payload)
            .send()
            .await
            .expect("Failed to create post");
        assert_eq!(res.status(), StatusCode::OK);
        let body: serde_json::Value = res.json().await.unwrap();
        let uri = body["uri"].as_str().unwrap();
        rkeys.push(uri.split('/').next_back().unwrap().to_string());
    }
    for rkey in &rkeys {
        let get_res = client
            .get(format!(
                "{}/xrpc/com.atproto.repo.getRecord?repo={}&collection=app.bsky.feed.post&rkey={}",
                base_url().await,
                did,
                rkey
            ))
            .send()
            .await
            .expect("Failed to get record before export");
        assert_eq!(
            get_res.status(),
            StatusCode::OK,
            "Record {} not found before export",
            rkey
        );
    }
    let export_res = client
        .get(format!(
            "{}/xrpc/com.atproto.sync.getRepo?did={}",
            base_url().await,
            did
        ))
        .send()
        .await
        .expect("Failed to export repo");
    assert_eq!(export_res.status(), StatusCode::OK);
    let car_bytes = export_res.bytes().await.unwrap();
    let import_res = client
        .post(format!(
            "{}/xrpc/com.atproto.repo.importRepo",
            base_url().await
        ))
        .bearer_auth(&token)
        .header("Content-Type", "application/vnd.ipld.car")
        .body(car_bytes.to_vec())
        .send()
        .await
        .expect("Failed to import repo");
    let status = import_res.status();
    if status != StatusCode::OK {
        let body = import_res.text().await.unwrap_or_default();
        panic!("Import failed with status {}: {}", status, body);
    }
    let list_res = client
        .get(format!(
            "{}/xrpc/com.atproto.repo.listRecords?repo={}&collection=app.bsky.feed.post",
            base_url().await,
            did
        ))
        .send()
        .await
        .expect("Failed to list records after import");
    assert_eq!(list_res.status(), StatusCode::OK);
    let list_body: serde_json::Value = list_res.json().await.unwrap();
    let records_after = list_body["records"]
        .as_array()
        .map(|a| a.len())
        .unwrap_or(0);
    assert!(
        records_after >= 1,
        "Expected at least 1 record after import, found {}. Note: MST walk may have timing issues.",
        records_after
    );
}
