use std::fs;

use assert_cmd::cargo::cargo_bin_cmd;
use earl::output::human::render_human_output;
use earl::template::schema::{ResultDecode, ResultTemplate};
use serde_json::{Map, json};

fn write_template(path: &std::path::Path, content: &str) {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent).unwrap();
    }
    fs::write(path, content).unwrap();
}

fn write_global_network_policy(home: &std::path::Path) {
    let config_path = home.join(".config/earl/config.toml");
    if let Some(parent) = config_path.parent() {
        fs::create_dir_all(parent).unwrap();
    }
    fs::write(
        config_path,
        r#"
[network]
allow_cli_commands = ["cargo"]

[[network.allow]]
scheme = "https"
host = "api.example.com"
port = 443
path_prefix = "/"
"#,
    )
    .unwrap();
}

#[test]
fn write_mode_requires_confirmation_without_yes() {
    let cwd = tempfile::tempdir().unwrap();
    let home = tempfile::tempdir().unwrap();
    write_global_network_policy(home.path());

    let local_template = r#"
version = 1
provider = "demo"

command "mutate" {
  title = "Mutate"
  summary = "Execute a write mutation"
  description = "Sends a write mutation request to the demo API."

  annotations {
    mode = "write"
    secrets = []
  }

  operation {
    protocol = "http"
    method = "POST"
    url = "https://api.example.com/mutate"
  }

  result {
    output = "ok"
  }
}
"#;

    write_template(&cwd.path().join("templates/demo.hcl"), local_template);

    let mut cmd = cargo_bin_cmd!("earl");
    cmd.current_dir(cwd.path())
        .env("HOME", home.path())
        .args(["call", "demo.mutate()"])
        .write_stdin("NO\n");

    let out = cmd.assert().failure().get_output().stderr.clone();
    let stderr = String::from_utf8(out).unwrap();
    assert!(stderr.contains("write command aborted"));
}

#[test]
fn local_template_precedence_applies_to_call_resolution() {
    let cwd = tempfile::tempdir().unwrap();
    let home = tempfile::tempdir().unwrap();
    write_global_network_policy(home.path());

    let global_template = r#"
version = 1
provider = "demo"

command "ping" {
  title = "Ping"
  summary = "Ping endpoint with query"
  description = "Calls the demo ping endpoint with a required query parameter."

  annotations {
    mode = "read"
    secrets = []
  }

  param "query" {
    type = "string"
    required = true
  }

  operation {
    protocol = "http"
    method = "GET"
    url = "https://api.example.com/ping"
  }

  result {
    output = "ok"
  }
}
"#;

    let local_template = r#"
version = 1
provider = "demo"

command "ping" {
  title = "Ping"
  summary = "Ping endpoint with local override"
  description = "Calls the overridden local ping endpoint."

  annotations {
    mode = "read"
    secrets = []
  }

  operation {
    protocol = "http"
    method = "GET"
    url = "not-a-valid-url"
  }

  result {
    output = "ok"
  }
}
"#;

    write_template(
        &home.path().join(".config/earl/templates/demo.hcl"),
        global_template,
    );
    write_template(&cwd.path().join("templates/demo.hcl"), local_template);

    let mut cmd = cargo_bin_cmd!("earl");
    cmd.current_dir(cwd.path())
        .env("HOME", home.path())
        .args(["call", "demo.ping()"]);

    let out = cmd.assert().failure().get_output().stderr.clone();
    let stderr = String::from_utf8(out).unwrap();
    assert!(stderr.contains("rendered invalid operation URL"));
    assert!(!stderr.contains("missing required argument `query`"));
}

#[test]
fn output_rendering_context_does_not_expose_secrets() {
    let result_template = ResultTemplate {
        decode: ResultDecode::Json,
        extract: None,
        output: "{{ result.ok }} {{ secrets.github.token }}".to_string(),
        result_alias: None,
    };

    let args = Map::new();
    let result = json!({"ok": true});

    let err = render_human_output(&result_template, &args, &result).unwrap_err();
    assert!(err.to_string().contains("template render failed"));
}

#[test]
fn cli_protocol_executes_local_command() {
    let cwd = tempfile::tempdir().unwrap();
    let home = tempfile::tempdir().unwrap();
    write_global_network_policy(home.path());

    let local_template = r#"
version = 1
provider = "demo"

command "version" {
  title = "Version"
  summary = "Show cargo version"
  description = "Executes the local cargo binary and returns the version string."

  annotations {
    mode = "read"
    secrets = []
  }

  operation {
    protocol = "cli"

    cli {
      command = "cargo"
      args = ["--version"]
    }
  }

  result {
    decode = "text"
    output = "{{ result }}"
  }
}
"#;

    write_template(&cwd.path().join("templates/demo.hcl"), local_template);

    let mut cmd = cargo_bin_cmd!("earl");
    cmd.current_dir(cwd.path())
        .env("HOME", home.path())
        .args(["call", "demo.version()"]);

    let out = cmd.assert().success().get_output().stdout.clone();
    let stdout = String::from_utf8(out).unwrap();
    assert!(
        stdout.to_ascii_lowercase().contains("cargo"),
        "unexpected CLI output: {stdout}"
    );
}
