mod common;

use common::http_server::TestHttpServer;
use common::{
    cli_cmd, parse_created_id, query_manager, seed_grpc_request, seed_request,
    seed_websocket_request, seed_workspace,
};
use predicates::str::contains;
use tempfile::TempDir;
use yaak_models::models::HttpResponseState;

#[test]
fn show_and_delete_yes_round_trip() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let data_dir = temp_dir.path();
    seed_workspace(data_dir, "wk_test");

    let create_assert = cli_cmd(data_dir)
        .args([
            "request",
            "create",
            "wk_test",
            "--name",
            "Smoke Test",
            "--url",
            "https://example.com",
        ])
        .assert()
        .success();

    let request_id = parse_created_id(&create_assert.get_output().stdout, "request create");

    cli_cmd(data_dir)
        .args(["request", "show", &request_id])
        .assert()
        .success()
        .stdout(contains(format!("\"id\": \"{request_id}\"")))
        .stdout(contains("\"workspaceId\": \"wk_test\""));

    cli_cmd(data_dir)
        .args(["request", "delete", &request_id, "--yes"])
        .assert()
        .success()
        .stdout(contains(format!("Deleted request: {request_id}")));

    assert!(query_manager(data_dir).connect().get_http_request(&request_id).is_err());
}

#[test]
fn delete_without_yes_fails_in_non_interactive_mode() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let data_dir = temp_dir.path();
    seed_workspace(data_dir, "wk_test");
    seed_request(data_dir, "wk_test", "rq_seed_delete_noninteractive");

    cli_cmd(data_dir)
        .args(["request", "delete", "rq_seed_delete_noninteractive"])
        .assert()
        .failure()
        .code(1)
        .stderr(contains("Refusing to delete in non-interactive mode without --yes"));

    assert!(
        query_manager(data_dir).connect().get_http_request("rq_seed_delete_noninteractive").is_ok()
    );
}

#[test]
fn json_create_and_update_merge_patch_round_trip() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let data_dir = temp_dir.path();
    seed_workspace(data_dir, "wk_test");

    let create_assert = cli_cmd(data_dir)
        .args([
            "request",
            "create",
            r#"{"workspaceId":"wk_test","name":"Json Request","url":"https://example.com"}"#,
        ])
        .assert()
        .success();
    let request_id = parse_created_id(&create_assert.get_output().stdout, "request create");

    cli_cmd(data_dir)
        .args([
            "request",
            "update",
            &format!(r#"{{"id":"{}","name":"Renamed Request"}}"#, request_id),
        ])
        .assert()
        .success()
        .stdout(contains(format!("Updated request: {request_id}")));

    cli_cmd(data_dir)
        .args(["request", "show", &request_id])
        .assert()
        .success()
        .stdout(contains("\"name\": \"Renamed Request\""))
        .stdout(contains("\"url\": \"https://example.com\""));
}

#[test]
fn update_requires_id_in_json_payload() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let data_dir = temp_dir.path();

    cli_cmd(data_dir)
        .args(["request", "update", r#"{"name":"No ID"}"#])
        .assert()
        .failure()
        .stderr(contains("request update requires a non-empty \"id\" field"));
}

#[test]
fn create_allows_workspace_only_with_empty_defaults() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let data_dir = temp_dir.path();
    seed_workspace(data_dir, "wk_test");

    let create_assert = cli_cmd(data_dir).args(["request", "create", "wk_test"]).assert().success();
    let request_id = parse_created_id(&create_assert.get_output().stdout, "request create");

    let request = query_manager(data_dir)
        .connect()
        .get_http_request(&request_id)
        .expect("Failed to load created request");
    assert_eq!(request.workspace_id, "wk_test");
    assert_eq!(request.method, "GET");
    assert_eq!(request.name, "");
    assert_eq!(request.url, "");
}

#[test]
fn create_merges_positional_workspace_id_into_json_payload() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let data_dir = temp_dir.path();
    seed_workspace(data_dir, "wk_test");

    let create_assert = cli_cmd(data_dir)
        .args([
            "request",
            "create",
            "wk_test",
            "--json",
            r#"{"name":"Merged Request","url":"https://example.com"}"#,
        ])
        .assert()
        .success();
    let request_id = parse_created_id(&create_assert.get_output().stdout, "request create");

    cli_cmd(data_dir)
        .args(["request", "show", &request_id])
        .assert()
        .success()
        .stdout(contains("\"workspaceId\": \"wk_test\""))
        .stdout(contains("\"name\": \"Merged Request\""));
}

#[test]
fn create_rejects_conflicting_workspace_ids_between_arg_and_json() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let data_dir = temp_dir.path();
    seed_workspace(data_dir, "wk_test");
    seed_workspace(data_dir, "wk_other");

    cli_cmd(data_dir)
        .args([
            "request",
            "create",
            "wk_test",
            "--json",
            r#"{"workspaceId":"wk_other","name":"Mismatch"}"#,
        ])
        .assert()
        .failure()
        .stderr(contains(
            "request create got conflicting workspace_id values between positional arg and JSON payload",
        ));
}

#[test]
fn request_send_persists_response_body_and_events() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let data_dir = temp_dir.path();
    seed_workspace(data_dir, "wk_test");

    let server = TestHttpServer::spawn_ok("hello from integration test");

    let create_assert = cli_cmd(data_dir)
        .args([
            "request",
            "create",
            "wk_test",
            "--name",
            "Send Test",
            "--url",
            &server.url,
        ])
        .assert()
        .success();
    let request_id = parse_created_id(&create_assert.get_output().stdout, "request create");

    cli_cmd(data_dir)
        .args(["request", "send", &request_id])
        .assert()
        .success()
        .stdout(contains("hello from integration test"));

    let qm = query_manager(data_dir);
    let db = qm.connect();
    let responses =
        db.list_http_responses_for_request(&request_id, None).expect("Failed to load responses");
    assert_eq!(responses.len(), 1, "expected exactly one persisted response");

    let response = &responses[0];
    assert_eq!(response.status, 200);
    assert!(matches!(response.state, HttpResponseState::Closed));
    assert!(response.error.is_none());

    let body_path =
        response.body_path.as_ref().expect("expected persisted response body path").to_string();
    let body = std::fs::read_to_string(&body_path).expect("Failed to read response body file");
    assert_eq!(body, "hello from integration test");

    let events =
        db.list_http_response_events(&response.id).expect("Failed to load response events");
    assert!(!events.is_empty(), "expected at least one persisted response event");
}

#[test]
fn request_schema_http_outputs_json_schema() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let data_dir = temp_dir.path();

    cli_cmd(data_dir)
        .args(["request", "schema", "http"])
        .assert()
        .success()
        .stdout(contains("\"type\":\"object\""))
        .stdout(contains("\"x-yaak-agent-hints\""))
        .stdout(contains("\"templateVariableSyntax\":\"${[ my_var ]}\""))
        .stdout(contains(
            "\"templateFunctionSyntax\":\"${[ namespace.my_func(a='aaa',b='bbb') ]}\"",
        ))
        .stdout(contains("\"authentication\":"))
        .stdout(contains("/foo/:id/comments/:commentId"))
        .stdout(contains("put concrete values in `urlParameters`"));
}

#[test]
fn request_schema_http_pretty_prints_with_flag() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let data_dir = temp_dir.path();

    cli_cmd(data_dir)
        .args(["request", "schema", "http", "--pretty"])
        .assert()
        .success()
        .stdout(contains("\"type\": \"object\""))
        .stdout(contains("\"authentication\""));
}

#[test]
fn request_send_grpc_returns_explicit_nyi_error() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let data_dir = temp_dir.path();
    seed_workspace(data_dir, "wk_test");
    seed_grpc_request(data_dir, "wk_test", "gr_seed_nyi");

    cli_cmd(data_dir)
        .args(["request", "send", "gr_seed_nyi"])
        .assert()
        .failure()
        .code(1)
        .stderr(contains("gRPC request send is not implemented yet in yaak-cli"));
}

#[test]
fn request_send_websocket_returns_explicit_nyi_error() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let data_dir = temp_dir.path();
    seed_workspace(data_dir, "wk_test");
    seed_websocket_request(data_dir, "wk_test", "wr_seed_nyi");

    cli_cmd(data_dir)
        .args(["request", "send", "wr_seed_nyi"])
        .assert()
        .failure()
        .code(1)
        .stderr(contains("WebSocket request send is not implemented yet in yaak-cli"));
}
