/// Integration tests for `phantom cloud push` and `phantom cloud pull`.
///
/// Architecture note on authentication
/// ------------------------------------
/// `auth::load_token()` reads from the OS keychain — there is no env-var
/// override today. As a result, tests that need to exercise the full push/pull
/// path against a wiremock server are marked `#[ignore]` with an explanation;
/// they can be run manually once the keychain token is present.
///
/// What *is* tested unconditionally:
///   - `phantom cloud push` / `pull` exit non-zero and print a helpful message
///     when no token is stored (the most important safety net).
///   - `phantom cloud status` succeeds and reports "not logged in" when no
///     token is present.
///   - When `PHANTOM_API_URL` points to a wiremock server and a token IS
///     available, push and pull exercise the correct HTTP verbs and paths
///     (skipped in CI where the keychain is empty).
use assert_cmd::Command;
use std::fs;
use tempfile::TempDir;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

const VAULT_PASS: &str = "test-integration-passphrase-cloud";

fn init_project(dir: &TempDir) {
    fs::write(dir.path().join(".env"), "SEED_SECRET=sk-seed-value\n").expect("write .env");
    Command::cargo_bin("phantom")
        .expect("binary not found")
        .args(["init", "--from", ".env"])
        .current_dir(dir.path())
        .env("PHANTOM_VAULT_PASSPHRASE", VAULT_PASS)
        .env("HOME", dir.path())
        .assert()
        .success();
}

fn phantom(dir: &TempDir) -> Command {
    let mut cmd = Command::cargo_bin("phantom").expect("binary not found");
    cmd.current_dir(dir.path())
        .env("PHANTOM_VAULT_PASSPHRASE", VAULT_PASS)
        .env("HOME", dir.path());
    cmd
}

// ──────────────────────────────────────────────────────────────────────────────
// Unconditional tests — no keychain token required
// ──────────────────────────────────────────────────────────────────────────────

#[test]
fn cloud_push_fails_without_auth_token() {
    let dir = TempDir::new().unwrap();
    init_project(&dir);

    // Use a mock server URL so we don't hit the real API. The command should
    // fail at the auth-token check before even making a network request.
    phantom(&dir)
        .args(["cloud", "push"])
        .env("PHANTOM_API_URL", "http://127.0.0.1:1") // nothing listening — irrelevant
        .assert()
        .failure();
}

#[test]
fn cloud_pull_fails_without_auth_token() {
    let dir = TempDir::new().unwrap();
    init_project(&dir);

    phantom(&dir)
        .args(["cloud", "pull"])
        .env("PHANTOM_API_URL", "http://127.0.0.1:1")
        .assert()
        .failure();
}

#[test]
fn cloud_status_succeeds_and_reports_not_logged_in() {
    // cloud status should always exit 0 and print a message — even without a
    // token it reports "not logged in" rather than erroring out.
    let dir = TempDir::new().unwrap();
    init_project(&dir);

    let output = phantom(&dir)
        .args(["cloud", "status"])
        .env("PHANTOM_API_URL", "http://127.0.0.1:1") // won't be reached
        .assert()
        .success();

    let stdout = String::from_utf8_lossy(&output.get_output().stdout);
    // Either "not logged in" or "logged in" — both are valid depending on
    // whether a real token is present in the developer's keychain.
    let has_expected_text = stdout.contains("not logged in")
        || stdout.contains("logged in")
        || stdout.contains("Cloud:");
    assert!(
        has_expected_text,
        "cloud status should report login state, got: {stdout}"
    );
}

// ──────────────────────────────────────────────────────────────────────────────
// Wiremock tests — require a keychain token; ignored in normal CI runs.
// ──────────────────────────────────────────────────────────────────────────────

/// Run `phantom cloud push` against a wiremock server and assert the correct
/// HTTP request is made.
///
/// # How to run manually
/// ```bash
/// # First ensure `phantom login` has been run (stores a token in keychain)
/// cargo test -p phantom-cli --test cloud_test -- --ignored
/// ```
#[tokio::test]
#[ignore = "requires an auth token in the OS keychain; run manually with `cargo test -- --ignored`"]
async fn cloud_push_calls_vault_push_endpoint() {
    let dir = TempDir::new().unwrap();
    init_project(&dir);

    let mock_server = MockServer::start().await;
    let api_url = mock_server.uri();

    // The push command hits PUT /vault/push
    Mock::given(method("PUT"))
        .and(path("/vault/push"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "version": 1
        })))
        .expect(1)
        .mount(&mock_server)
        .await;

    phantom(&dir)
        .args(["cloud", "push"])
        .env("PHANTOM_API_URL", &api_url)
        .assert()
        .success();

    mock_server.verify().await;
}

/// Run `phantom cloud pull` against a wiremock server that returns a 404 (no
/// vault yet) and assert the command exits 0 with a "no cloud vault" message.
#[tokio::test]
#[ignore = "requires an auth token in the OS keychain; run manually with `cargo test -- --ignored`"]
async fn cloud_pull_handles_404_gracefully() {
    let dir = TempDir::new().unwrap();
    init_project(&dir);

    let mock_server = MockServer::start().await;
    let api_url = mock_server.uri();

    // GET /vault/pull returns 404 — no vault exists yet
    Mock::given(method("GET"))
        .and(path("/vault/pull"))
        .respond_with(ResponseTemplate::new(404))
        .expect(1)
        .mount(&mock_server)
        .await;

    let output = phantom(&dir)
        .args(["cloud", "pull"])
        .env("PHANTOM_API_URL", &api_url)
        .assert()
        .success();

    let stdout = String::from_utf8_lossy(&output.get_output().stdout);
    assert!(
        stdout.contains("No cloud vault") || stdout.contains("no cloud vault"),
        "should report no vault found, got: {stdout}"
    );

    mock_server.verify().await;
}

/// Full push → pull round-trip via wiremock.
#[tokio::test]
#[ignore = "requires an auth token in the OS keychain; run manually with `cargo test -- --ignored`"]
async fn cloud_push_then_pull_round_trip() {
    let dir_push = TempDir::new().unwrap();
    let dir_pull = TempDir::new().unwrap();
    init_project(&dir_push);
    init_project(&dir_pull);

    let mock_server = MockServer::start().await;
    let api_url = mock_server.uri();

    // We can't easily intercept the request body with wiremock's simple API,
    // so we just assert the correct verbs land on the right paths in order.
    Mock::given(method("PUT"))
        .and(path("/vault/push"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "version": 1
        })))
        .expect(1)
        .mount(&mock_server)
        .await;

    Mock::given(method("GET"))
        .and(path("/vault/pull"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "encrypted_blob": "dGVzdA==",  // base64("test") — decryption will fail, that's ok
            "version": 1
        })))
        .expect(1)
        .mount(&mock_server)
        .await;

    // Push
    phantom(&dir_push)
        .args(["cloud", "push"])
        .env("PHANTOM_API_URL", &api_url)
        .assert()
        .success();

    // Pull (may fail at decryption — that's acceptable for this HTTP-layer test)
    let _ = phantom(&dir_pull)
        .args(["cloud", "pull"])
        .env("PHANTOM_API_URL", &api_url)
        .output();

    // Both HTTP endpoints must have been hit
    mock_server.verify().await;
}
