// Copyright 2022 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

#![cfg(not(target_os = "wasi"))]
use std::{
    error::Error,
    fs::{self, create_dir_all},
    path::PathBuf,
    process::Command,
};

// Add methods on commands
use assert_cmd::{cargo, prelude::*};
use httpmock::{prelude::*, Mock};
use predicate::str;
use predicates::prelude::*;
use serde_json::Value;
use tempfile::tempdir;

const TEST_IMAGE: &str = "earth_apollo17.jpg";
//const TEST_IMAGE: &str = "libpng-test.png"; // save for png testing
const TEST_IMAGE_WITH_MANIFEST: &str = "C.jpg"; // save for manifest tests

fn fixture_path(name: &str) -> PathBuf {
    let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    path.push("tests/fixtures");
    path.push(name);
    fs::canonicalize(path).expect("canonicalize")
}

fn temp_path(name: &str) -> PathBuf {
    let path = PathBuf::from(env!("CARGO_TARGET_TMPDIR"));
    create_dir_all(&path).ok();
    path.join(name)
}

#[test]
fn tool_not_found() -> Result<(), Box<dyn Error>> {
    let mut cmd = Command::new(cargo::cargo_bin!("c2patool"));
    cmd.arg("test/file/notfound.jpg");
    cmd.assert().failure().stderr(str::contains("os error"));
    Ok(())
}
#[test]
fn tool_not_found_info() -> Result<(), Box<dyn Error>> {
    let mut cmd = Command::new(cargo::cargo_bin!("c2patool"));
    cmd.arg("test/file/notfound.jpg").arg("--info");
    cmd.assert()
        .failure()
        .stderr(str::contains("file not found"));
    Ok(())
}
#[test]
fn tool_jpeg_no_report() -> Result<(), Box<dyn Error>> {
    let mut cmd = Command::new(cargo::cargo_bin!("c2patool"));
    cmd.arg(fixture_path(TEST_IMAGE));
    cmd.assert()
        .failure()
        .stderr(str::contains("No claim found"));
    Ok(())
}

#[test]
// c2patool tests/fixtures/C.jpg --info
fn tool_info() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST))
        .arg("--info")
        .assert()
        .success()
        .stdout(str::contains(
            "Provenance URI = self#jumbf=/c2pa/contentauth:urn:uuid:",
        ))
        .stdout(str::contains("Manifest store size = 51217"));
    Ok(())
}

#[test]
fn tool_embed_jpeg_report() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE))
        .arg("-m")
        .arg("sample/test.json")
        .arg("-p")
        .arg(fixture_path(TEST_IMAGE))
        .arg("-o")
        .arg(temp_path("out.jpg"))
        .arg("-f")
        .assert()
        .success() // should this be a failure?
        .stdout(str::contains("My Title"));
    Ok(())
}

#[test]
fn tool_fs_output_report() -> Result<(), Box<dyn Error>> {
    let path = temp_path("output_dir");
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path("verify.jpeg"))
        .arg("-o")
        .arg(&path)
        .arg("-f")
        .assert()
        .success()
        .stdout(str::contains(format!(
            "Manifest report written to the directory {path:?}"
        )));
    let manifest_json = path.join("manifest_store.json");
    let contents = fs::read_to_string(manifest_json)?;
    let json: Value = serde_json::from_str(&contents)?;
    assert_eq!(
        json.as_object()
            .unwrap()
            .get("active_manifest")
            .unwrap()
            .as_str()
            .unwrap(),
        "adobe:urn:uuid:df1d2745-5beb-4d6c-bd99-3527e29c7df0",
    );
    Ok(())
}
#[test]
fn tool_fs_output_report_supports_detailed_flag() -> Result<(), Box<dyn Error>> {
    let path = temp_path("./output_detailed");
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path("verify.jpeg"))
        .arg("-o")
        .arg(&path)
        .arg("-f")
        .arg("-d")
        .assert()
        .success()
        .stdout(str::contains(format!(
            "Manifest report written to the directory {path:?}"
        )));
    let manifest_json = path.join("detailed.json");
    let contents = fs::read_to_string(manifest_json)?;
    let json: Value = serde_json::from_str(&contents)?;
    assert!(json
        .as_object()
        .unwrap()
        .get("validation_results")
        .is_some());
    Ok(())
}
#[test]
fn tool_fs_output_fails_when_output_exists() -> Result<(), Box<dyn Error>> {
    let path = temp_path("./output_conflict");
    // Create conflict directory.
    create_dir_all(&path)?;
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path("C.jpg"))
        .arg("-o")
        .arg(&path)
        .assert()
        .failure()
        .stderr(str::contains(
            "Error: Output already exists; use -f/force to force write",
        ));
    Ok(())
}
#[test]
// c2patool tests/fixtures/C.jpg -fo target/tmp/manifest_test
fn tool_test_manifest_folder() -> Result<(), Box<dyn std::error::Error>> {
    let out_path = temp_path("manifest_test");
    // first export a c2pa file
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST))
        .arg("-o")
        .arg(&out_path)
        .arg("-f")
        .assert()
        .success()
        .stdout(str::contains("Manifest report written"));
    // then read it back in
    let json =
        std::fs::read_to_string(out_path.join("manifest_store.json")).expect("read manifest");
    dbg!(&json);
    assert!(json.contains("make_test_images"));
    Ok(())
}
#[test]
// c2patool tests/fixtures/C.jpg -ifo target/tmp/ingredient_test
fn tool_test_ingredient_folder() -> Result<(), Box<dyn std::error::Error>> {
    let out_path = temp_path("ingredient_test");
    // first export a c2pa file
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST))
        .arg("-o")
        .arg(&out_path)
        .arg("--ingredient")
        .arg("-f")
        .assert()
        .success()
        .stdout(str::contains("Ingredient report written"));
    // then read it back in
    let json = std::fs::read_to_string(out_path.join("ingredient.json")).expect("read manifest");
    assert!(json.contains("manifest_data"));
    Ok(())
}
#[test]
// c2patool tests/fixtures/C.jpg -ifo target/tmp/ingredient_json
// c2patool tests/fixtures/earth_apollo17.jpg -m sample/test.json -p target/tmp/ingredient_json/ingredient.json -fo target/tmp/out_2.jpg
fn tool_test_manifest_ingredient_json() -> Result<(), Box<dyn std::error::Error>> {
    let out_path = temp_path("ingredient_json");
    // first export a c2pa file
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST))
        .arg("-o")
        .arg(&out_path)
        .arg("--ingredient")
        .arg("-f")
        .assert()
        .success()
        .stdout(str::contains("Ingredient report written"));
    let json_path = out_path.join("ingredient.json");
    let parent = json_path.to_string_lossy().to_string();
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE))
        .arg("-p")
        .arg(parent)
        .arg("-m")
        .arg("sample/test.json")
        .arg("-o")
        .arg(temp_path("out_2.jpg"))
        .arg("-f")
        .assert()
        .success()
        .stdout(str::contains("My Title"));
    Ok(())
}
#[test]
// c2patool tests/fixtures/earth_apollo17.jpg -m tests/fixtures/ingredient_test.json -o target/tmp/ingredients.jpg -f
fn tool_embed_jpeg_with_ingredients_report() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE))
        .arg("-m")
        .arg(fixture_path("ingredient_test.json"))
        .arg("-o")
        .arg(temp_path("ingredients.jpg"))
        .arg("-f")
        .assert()
        .success()
        .stdout(str::contains("ingredients.jpg"))
        .stdout(str::contains("test ingredient"))
        .stdout(str::contains("temporal"))
        .stdout(str::contains("earth_apollo17.jpg"));
    Ok(())
}
#[test]
fn tool_extensions_do_not_match() -> Result<(), Box<dyn Error>> {
    let path = temp_path("./foo.png");
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path("C.jpg"))
        .arg("-m")
        .arg(fixture_path("ingredient_test.json"))
        .arg("-o")
        .arg(&path)
        .assert()
        .failure()
        .stderr(str::contains("Output type must match source type"));
    Ok(())
}
#[test]
fn tool_similar_extensions_match() -> Result<(), Box<dyn Error>> {
    let path = temp_path("./similar.JpEg");
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path("C.jpg"))
        .arg("-m")
        .arg(fixture_path("ingredient_test.json"))
        .arg("-o")
        .arg(&path)
        .arg("-f")
        .assert()
        .success()
        .stdout(str::contains("similar."));
    Ok(())
}
#[test]
fn tool_fail_if_thumbnail_missing() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE))
        .arg("-c")
        .arg("{\"thumbnail\": {\"identifier\": \"thumb.jpg\",\"format\": \"image/jpeg\"}}")
        .arg("-o")
        .arg(temp_path("out_thumb.jpg"))
        .arg("-f")
        .assert()
        .failure()
        .stderr(str::contains("resource not found"));
    Ok(())
}

#[test]
fn tool_sign_to_same_file_with_force() -> Result<(), Box<dyn Error>> {
    let tmp_dir = tempdir()?;
    let file_path = tmp_dir.path().join("same_image.jpg");
    fs::copy(fixture_path(TEST_IMAGE), &file_path)?;

    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(&file_path)
        .arg("-m")
        .arg(fixture_path("ingredient_test.json"))
        .arg("-o")
        .arg(&file_path)
        .arg("-f")
        .assert()
        .success()
        .stdout(str::contains("same_image.jpg"))
        .stdout(str::contains("test ingredient"))
        .stdout(str::contains("temporal"))
        .stdout(str::contains("earth_apollo17.jpg"));
    Ok(())
}

#[test]
fn tool_sign_to_same_file_no_force() -> Result<(), Box<dyn Error>> {
    let tmp_dir = tempdir()?;
    let file_path = tmp_dir.path().join("same_image.jpg");
    fs::copy(fixture_path(TEST_IMAGE), &file_path)?;

    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(&file_path)
        .arg("-m")
        .arg(fixture_path("ingredient_test.json"))
        .arg("-o")
        .arg(&file_path)
        .assert()
        .failure()
        .stderr(str::contains(
            "Error: Output already exists; use -f/force to force write",
        ));

    Ok(())
}
#[test]
fn test_sign_using_c2patool_as_subprocess_signer() -> Result<(), Box<dyn Error>> {
    let output = temp_path("output_subprocess_signer.jpg");
    // "c2patool test-signer" implements the subprocess signing protocol:
    // --signer-info returns the baked-in es256 cert; default mode signs stdin bytes.
    let signer_cmd = format!("{} test-signer", cargo::cargo_bin!("c2patool").display());
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE))
        .arg("--signer-path")
        .arg(&signer_cmd)
        .arg("--manifest")
        .arg("sample/test.json")
        .arg("-o")
        .arg(&output)
        .arg("-f")
        .assert()
        .success();
    Ok(())
}

#[test]
fn test_sign_cawg_using_c2patool_as_identity_signer() -> Result<(), Box<dyn Error>> {
    let output = temp_path("output_cawg_identity_signer.jpg");
    // Both --signer-path and --identity-signer-path use "c2patool test-signer".
    // --signer-info is called for each to discover the baked-in es256 cert and alg.
    let signer_cmd = format!("{} test-signer", cargo::cargo_bin!("c2patool").display());
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE))
        .arg("--signer-path")
        .arg(&signer_cmd)
        .arg("--identity-signer-path")
        .arg(&signer_cmd)
        .arg("--manifest")
        .arg("sample/test.json")
        .arg("-o")
        .arg(&output)
        .arg("-f")
        .assert()
        .success()
        .stdout(str::contains("cawg.identity"));
    Ok(())
}

#[test]
fn test_fails_for_not_found_external_signer() -> Result<(), Box<dyn Error>> {
    let output = temp_path("output_not_found_signer.jpg");
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE))
        .arg("--signer-path")
        .arg("./nonexistent-signer-binary-xyz")
        .arg("--manifest")
        .arg("sample/test.json")
        .arg("-o")
        .arg(&output)
        .arg("-f")
        .assert()
        .failure()
        .stderr(str::contains("Failed to run"));
    Ok(())
}

#[test]
fn test_fails_for_external_signer_failure() -> Result<(), Box<dyn Error>> {
    let output = temp_path("output_failing_signer.jpg");
    // "c2patool test-signer --fail" exits with an error, exercising the failure path.
    let signer_cmd = format!(
        "{} test-signer --fail",
        cargo::cargo_bin!("c2patool").display()
    );
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE))
        .arg("--signer-path")
        .arg(&signer_cmd)
        .arg("--manifest")
        .arg("sample/test.json")
        .arg("-o")
        .arg(&output)
        .arg("-f")
        .assert()
        .failure()
        .stderr(str::contains("deliberately failed"));
    Ok(())
}

#[test]
// With RUST_LOG unset, default level is `error` (see main) — debug! lines in configure_sdk must not appear.
fn rust_log_unset_suppresses_trust_debug_messages() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST))
        .arg("trust")
        .arg("--trust_anchors")
        .arg(fixture_path("trust/anchors.pem"))
        .arg("--trust_config")
        .arg(fixture_path("trust/store.cfg"))
        .env_remove("RUST_LOG")
        .assert()
        .success()
        .stderr(str::contains("Using trust anchors").not());
    Ok(())
}

#[test]
// With RUST_LOG=debug, trust setup emits debug! lines; proves the test above is exercising log level.
fn rust_log_debug_shows_trust_debug_messages() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST))
        .arg("trust")
        .arg("--trust_anchors")
        .arg(fixture_path("trust/anchors.pem"))
        .arg("--trust_config")
        .arg(fixture_path("trust/store.cfg"))
        .env("RUST_LOG", "debug")
        .assert()
        .success()
        .stderr(str::contains("Using trust"));
    Ok(())
}

#[test]
// c2patool tests/fixtures/C.jpg trust --trust_anchors=tests/fixtures/trust/anchors.pem --trust_config=tests/fixtures/trust/store.cfg
fn tool_load_trust_settings_from_file_trusted() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST))
        .arg("trust")
        .arg("--trust_anchors")
        .arg(fixture_path("trust/anchors.pem"))
        .arg("--trust_config")
        .arg(fixture_path("trust/store.cfg"))
        .assert()
        .success()
        .stdout(str::contains("C2PA Test Signing Cert"))
        .stdout(str::contains("signingCredential.untrusted").not());
    Ok(())
}

#[test]
// c2patool tests/fixtures/C.jpg trust --trust_anchors=tests/fixtures/trust/no-match.pem --trust_config=tests/fixtures/trust/store.cfg
fn tool_load_trust_settings_from_file_untrusted() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST))
        .arg("trust")
        .arg("--trust_anchors")
        .arg(fixture_path("trust/no-match.pem"))
        .arg("--trust_config")
        .arg(fixture_path("trust/store.cfg"))
        .assert()
        .success()
        .stdout(str::contains("C2PA Test Signing Cert"))
        .stdout(str::contains("signingCredential.untrusted"));
    Ok(())
}

fn create_mock_server<'a>(
    server: &'a MockServer,
    anchor_source: &str,
    config_source: &str,
) -> Vec<Mock<'a>> {
    let anchor_path = fixture_path(anchor_source).to_str().unwrap().to_owned();
    let trust_mock = server.mock(|when, then| {
        when.method(GET).path("/trust/anchors.pem");
        then.status(200)
            .header("content-type", "text/plain")
            .body_from_file(anchor_path);
    });
    let config_path = fixture_path(config_source).to_str().unwrap().to_owned();
    let config_mock = server.mock(|when, then| {
        when.method(GET).path("/trust/store.cfg");
        then.status(200)
            .header("content-type", "text/plain")
            .body_from_file(config_path);
    });

    vec![trust_mock, config_mock]
}

#[test]
fn tool_load_trust_settings_from_url_arg_trusted() -> Result<(), Box<dyn Error>> {
    let server = MockServer::start();
    let mocks = create_mock_server(&server, "trust/anchors.pem", "trust/store.cfg");

    // Test flags
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST))
        .arg("trust")
        .arg("--trust_anchors")
        .arg(server.url("/trust/anchors.pem"))
        .arg("--trust_config")
        .arg(server.url("/trust/store.cfg"))
        .assert()
        .success()
        .stdout(str::contains("C2PA Test Signing Cert"))
        .stdout(str::contains("signingCredential.untrusted").not());

    mocks.iter().for_each(|m| m.assert());

    Ok(())
}

#[test]
fn tool_load_trust_settings_from_url_arg_untrusted() -> Result<(), Box<dyn Error>> {
    let server = MockServer::start();
    let mocks = create_mock_server(&server, "trust/no-match.pem", "trust/store.cfg");

    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST))
        .arg("trust")
        .arg("--trust_anchors")
        .arg(server.url("/trust/anchors.pem"))
        .arg("--trust_config")
        .arg(server.url("/trust/store.cfg"))
        .assert()
        .success()
        .stdout(str::contains("C2PA Test Signing Cert"))
        .stdout(str::contains("signingCredential.untrusted"));

    mocks.iter().for_each(|m| m.assert());

    Ok(())
}

#[test]
fn tool_load_trust_settings_from_url_env_trusted() -> Result<(), Box<dyn Error>> {
    let server = MockServer::start();
    let mocks = create_mock_server(&server, "trust/anchors.pem", "trust/store.cfg");

    // Test flags
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST))
        .arg("trust")
        .env("C2PATOOL_TRUST_ANCHORS", server.url("/trust/anchors.pem"))
        .env("C2PATOOL_TRUST_CONFIG", server.url("/trust/store.cfg"))
        .assert()
        .success()
        .stdout(str::contains("C2PA Test Signing Cert"))
        .stdout(str::contains("signingCredential.untrusted").not());

    mocks.iter().for_each(|m| m.assert());

    Ok(())
}

#[test]
fn tool_load_trust_settings_from_url_env_untrusted() -> Result<(), Box<dyn Error>> {
    let server = MockServer::start();
    let mocks = create_mock_server(&server, "trust/no-match.pem", "trust/store.cfg");

    // Test flags
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST))
        .arg("trust")
        .env("C2PATOOL_TRUST_ANCHORS", server.url("/trust/anchors.pem"))
        .env("C2PATOOL_TRUST_CONFIG", server.url("/trust/store.cfg"))
        .assert()
        .success()
        .stdout(str::contains("C2PA Test Signing Cert"))
        .stdout(str::contains("signingCredential.untrusted"));

    mocks.iter().for_each(|m| m.assert());

    Ok(())
}

#[test]
// c2patool tests/fixtures/C.jpg --tree
fn tool_tree() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST))
        .arg("--tree")
        .assert()
        .success()
        .stdout(str::contains("Asset:C.jpg, Manifest:contentauth:urn:uuid:"))
        .stdout(str::contains("Assertion:c2pa.actions"));
    Ok(())
}

#[test]
// c2patool --settings .../trust/cawg_test_settings.toml C_with_CAWG_data.jpg
fn tool_read_image_with_cawg_data() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg("--settings")
        .arg(fixture_path("trust/cawg_test_settings.toml"))
        .arg(fixture_path("C_with_CAWG_data.jpg"))
        .assert()
        .success()
        .stdout(str::contains("cawg.identity"))
        .stdout(str::contains("c2pa.assertions/cawg.training-mining"))
        .stdout(str::contains("cawg.identity.well-formed"));
    Ok(())
}

#[test]
// c2patool --settings .../trust/cawg_test_settings.toml --detailed C_with_CAWG_data.jpg
fn tool_read_image_with_details_with_cawg_data() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg("--settings")
        .arg(fixture_path("trust/cawg_test_settings.toml"))
        .arg(fixture_path("C_with_CAWG_data.jpg"))
        .arg("--detailed")
        .assert()
        .success()
        .stdout(str::contains("assertion_store"))
        .stdout(str::contains("cawg.identity"))
        .stdout(str::contains("c2pa.assertions/cawg.training-mining"))
        .stdout(str::contains("cawg.identity.well-formed"));
    Ok(())
}

#[test]
// c2patool --settings .../trust/cawg_test_settings.toml C_with_CAWG_data.jpg
fn tool_sign_image_with_cawg_data() -> Result<(), Box<dyn Error>> {
    let tmp_dir = tempdir()?;
    let file_path = tmp_dir.path().join("same_image.jpg");
    fs::copy(fixture_path(TEST_IMAGE), &file_path)?;

    let output_path = tmp_dir.path().join("same_image_cawg_signed.jpg");

    Command::new(cargo::cargo_bin!("c2patool"))
        .arg("--settings")
        .arg(fixture_path("trust/cawg_sign_settings.toml"))
        .arg(&file_path)
        .arg("-m")
        .arg(fixture_path("ingredient_test.json"))
        .arg("-o")
        .arg(&output_path)
        .arg("-f")
        .assert()
        .success();

    Command::new(cargo::cargo_bin!("c2patool"))
        .arg("--settings")
        .arg(fixture_path("trust/cawg_sign_settings.toml"))
        .arg(&output_path)
        .assert()
        .success()
        .stdout(str::contains("cawg.identity"))
        .stdout(str::contains("c2pa.assertions/cawg.training-mining"));
    // .stdout(str::contains("cawg.identity.well-formed"));
    // ^^ Enable this when #1356 lands.
    Ok(())
}

#[test]
// c2patool --crjson C.jpg
fn tool_read_image_crjson() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg("--crjson")
        .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST))
        .assert()
        .success()
        .stdout(str::contains("\"jsonGenerator\""))
        .stdout(str::contains("https://c2pa.org/crjson"));
    Ok(())
}

// Minimal manifest definition with no actions; lets the SDK auto-inject the
// correct c2pa.created / c2pa.opened action based on intent.
const MINIMAL_MANIFEST: &str = r#"{"assertions": []}"#;

#[test]
// c2patool earth_apollo17.jpg --create digitalCapture -c '{"assertions":[]}' -o out.jpg -f
// Create intent: auto-injects c2pa.created with digitalCapture, no parent ingredient.
fn intent_create_adds_created_action() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE))
        .arg("--create")
        .arg("digitalCapture")
        .arg("-c")
        .arg(MINIMAL_MANIFEST)
        .arg("-o")
        .arg(temp_path("intent_create_out.jpg"))
        .arg("-f")
        .assert()
        .success()
        .stdout(str::contains("c2pa.created"))
        .stdout(str::contains("digitalCapture"))
        .stdout(str::contains("parentOf").not());
    Ok(())
}

#[test]
// --create and --parent are mutually exclusive at the CLI level.
fn intent_create_rejects_parent_flag() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE))
        .arg("--create")
        .arg("digitalCapture")
        .arg("--parent")
        .arg(fixture_path(TEST_IMAGE))
        .arg("-c")
        .arg(MINIMAL_MANIFEST)
        .arg("-o")
        .arg(temp_path("intent_create_parent_out.jpg"))
        .arg("-f")
        .assert()
        .failure()
        .stderr(str::contains("cannot be used with"));
    Ok(())
}

#[test]
// Default (no --create / --update flag) = Edit intent.
// SDK auto-adds source as parent ingredient and injects c2pa.opened tied to it.
fn intent_edit_default_adds_parent_and_opened_action() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE))
        .arg("-c")
        .arg(MINIMAL_MANIFEST)
        .arg("-o")
        .arg(temp_path("intent_edit_out.jpg"))
        .arg("-f")
        .assert()
        .success()
        .stdout(str::contains("c2pa.opened"))
        .stdout(str::contains("parentOf"));
    Ok(())
}

#[test]
// --update = Update manifest intent.
// The source must already have a manifest (update manifests require provenance on the parent).
// SDK auto-adds source as parent ingredient and injects c2pa.opened tied to it.
fn intent_update_adds_parent_and_opened_action() -> Result<(), Box<dyn Error>> {
    Command::new(cargo::cargo_bin!("c2patool"))
        .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST)) // C.jpg already has a manifest
        .arg("--update")
        .arg("-c")
        .arg(MINIMAL_MANIFEST)
        .arg("-o")
        .arg(temp_path("intent_update_out.jpg"))
        .arg("-f")
        .assert()
        .success()
        .stdout(str::contains("c2pa.opened"))
        .stdout(str::contains("parentOf"));
    Ok(())
}
