use std::fs;

use earl::template::loader::validate_all_from_dirs;
use tempfile::tempdir;

#[test]
fn validates_template_files() {
    let dir = tempdir().unwrap();
    let local_dir = dir.path().join("local");
    let global_dir = dir.path().join("global");
    fs::create_dir_all(&local_dir).unwrap();
    fs::create_dir_all(&global_dir).unwrap();

    fs::write(
        local_dir.join("valid.hcl"),
        include_str!("fixtures/templates/valid_minimal.hcl"),
    )
    .unwrap();

    let files = validate_all_from_dirs(&global_dir, &local_dir).unwrap();
    assert_eq!(files.len(), 1);
    assert!(files[0].to_string_lossy().contains("valid.hcl"));
}

#[test]
fn allows_empty_allowlist_rule_set() {
    let dir = tempdir().unwrap();
    let local_dir = dir.path().join("local");
    let global_dir = dir.path().join("global");
    fs::create_dir_all(&local_dir).unwrap();
    fs::create_dir_all(&global_dir).unwrap();

    fs::write(
        local_dir.join("invalid.hcl"),
        include_str!("fixtures/templates/invalid_missing_allow.hcl"),
    )
    .unwrap();

    let files = validate_all_from_dirs(&global_dir, &local_dir).unwrap();
    assert_eq!(files.len(), 1);
    assert!(files[0].to_string_lossy().contains("invalid.hcl"));
}

#[test]
fn fails_on_undeclared_auth_secret_reference() {
    let dir = tempdir().unwrap();
    let local_dir = dir.path().join("local");
    let global_dir = dir.path().join("global");
    fs::create_dir_all(&local_dir).unwrap();
    fs::create_dir_all(&global_dir).unwrap();

    fs::write(
        local_dir.join("invalid_secret.hcl"),
        include_str!("fixtures/templates/invalid_secret_ref.hcl"),
    )
    .unwrap();

    let err = validate_all_from_dirs(&global_dir, &local_dir).unwrap_err();
    let rendered = format!("{err:#}");
    assert!(
        rendered.contains("is not declared in annotations.secrets"),
        "unexpected error: {rendered}"
    );
}

#[test]
fn fails_on_invalid_multipart_part_definition() {
    let dir = tempdir().unwrap();
    let local_dir = dir.path().join("local");
    let global_dir = dir.path().join("global");
    fs::create_dir_all(&local_dir).unwrap();
    fs::create_dir_all(&global_dir).unwrap();

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

command "upload" {
  title = "Upload"
  summary = "Upload multipart payload"
  description = "Uploads multipart content to the API."

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

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

    body {
      kind = "multipart"
      parts = [
        {
          name = "payload"
          value = "hello"
          file_path = "/tmp/a"
        }
      ]
    }
  }

  result {
    output = "ok"
  }
}
"#;
    fs::write(local_dir.join("invalid_multipart.hcl"), hcl).unwrap();

    let err = validate_all_from_dirs(&global_dir, &local_dir).unwrap_err();
    let rendered = format!("{err:#}");
    assert!(
        rendered.contains("must specify exactly one of value, bytes_base64, file_path"),
        "unexpected error: {rendered}"
    );
}

#[test]
fn fails_when_graphql_protocol_missing_graphql_block() {
    let dir = tempdir().unwrap();
    let local_dir = dir.path().join("local");
    let global_dir = dir.path().join("global");
    fs::create_dir_all(&local_dir).unwrap();
    fs::create_dir_all(&global_dir).unwrap();

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

command "query" {
  title = "Query"
  summary = "Run GraphQL query"
  description = "Runs a GraphQL query against the API."

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

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

  result {
    output = "ok"
  }
}
"#;
    fs::write(local_dir.join("invalid_graphql.hcl"), hcl).unwrap();

    let err = validate_all_from_dirs(&global_dir, &local_dir).unwrap_err();
    let rendered = format!("{err:#}");
    assert!(
        rendered.contains("missing field `graphql`"),
        "unexpected error: {rendered}"
    );
}

#[test]
fn fails_when_cli_protocol_missing_cli_block() {
    let dir = tempdir().unwrap();
    let local_dir = dir.path().join("local");
    let global_dir = dir.path().join("global");
    fs::create_dir_all(&local_dir).unwrap();
    fs::create_dir_all(&global_dir).unwrap();

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

command "run" {
  title = "Run"
  summary = "Run local command"
  description = "Executes a local CLI command."

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

  operation {
    protocol = "cli"
  }

  result {
    output = "ok"
  }
}
"#;
    fs::write(local_dir.join("invalid_cli.hcl"), hcl).unwrap();

    let err = validate_all_from_dirs(&global_dir, &local_dir).unwrap_err();
    let rendered = format!("{err:#}");
    assert!(
        rendered.contains("missing field `cli`"),
        "unexpected error: {rendered}"
    );
}

#[test]
fn fails_when_cli_protocol_includes_http_fields() {
    let dir = tempdir().unwrap();
    let local_dir = dir.path().join("local");
    let global_dir = dir.path().join("global");
    fs::create_dir_all(&local_dir).unwrap();
    fs::create_dir_all(&global_dir).unwrap();

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

command "run" {
  title = "Run"
  summary = "Run local command"
  description = "Executes a local CLI command."

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

  operation {
    protocol = "cli"
    method = "GET"

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

  result {
    output = "ok"
  }
}
"#;
    fs::write(local_dir.join("invalid_cli_http_fields.hcl"), hcl).unwrap();

    let err = validate_all_from_dirs(&global_dir, &local_dir).unwrap_err();
    let rendered = format!("{err:#}");
    assert!(
        rendered.contains("unknown field `method`"),
        "unexpected error: {rendered}"
    );
}

#[test]
fn fails_when_grpc_protocol_missing_grpc_block() {
    let dir = tempdir().unwrap();
    let local_dir = dir.path().join("local");
    let global_dir = dir.path().join("global");
    fs::create_dir_all(&local_dir).unwrap();
    fs::create_dir_all(&global_dir).unwrap();

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

command "check" {
  title = "Check"
  summary = "Run gRPC health check"
  description = "Calls a gRPC endpoint."

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

  operation {
    protocol = "grpc"
    url = "http://127.0.0.1:50051"
  }

  result {
    output = "ok"
  }
}
"#;
    fs::write(local_dir.join("invalid_grpc.hcl"), hcl).unwrap();

    let err = validate_all_from_dirs(&global_dir, &local_dir).unwrap_err();
    let rendered = format!("{err:#}");
    assert!(
        rendered.contains("missing field `grpc`"),
        "unexpected error: {rendered}"
    );
}

#[test]
fn fails_when_grpc_auth_api_key_uses_query_location() {
    let dir = tempdir().unwrap();
    let local_dir = dir.path().join("local");
    let global_dir = dir.path().join("global");
    fs::create_dir_all(&local_dir).unwrap();
    fs::create_dir_all(&global_dir).unwrap();

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

command "check" {
  title = "Check"
  summary = "Run gRPC health check"
  description = "Calls a gRPC endpoint."

  annotations {
    mode = "read"
    secrets = ["api.key"]
  }

  operation {
    protocol = "grpc"
    url = "http://127.0.0.1:50051"

    auth {
      kind = "api_key"
      location = "query"
      name = "token"
      secret = "api.key"
    }

    grpc {
      service = "grpc.health.v1.Health"
      method = "Check"
      body = { service = "" }
    }
  }

  result {
    output = "ok"
  }
}
"#;
    fs::write(local_dir.join("invalid_grpc_api_key.hcl"), hcl).unwrap();

    let err = validate_all_from_dirs(&global_dir, &local_dir).unwrap_err();
    let rendered = format!("{err:#}");
    assert!(
        rendered.contains("grpc auth api_key location must be `header`"),
        "unexpected error: {rendered}"
    );
}

#[test]
fn fails_when_grpc_uses_unsupported_proxy_profile() {
    let dir = tempdir().unwrap();
    let local_dir = dir.path().join("local");
    let global_dir = dir.path().join("global");
    fs::create_dir_all(&local_dir).unwrap();
    fs::create_dir_all(&global_dir).unwrap();

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

command "check" {
  title = "Check"
  summary = "Run gRPC health check"
  description = "Calls a gRPC endpoint."

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

  operation {
    protocol = "grpc"
    url = "http://127.0.0.1:50051"

    grpc {
      service = "grpc.health.v1.Health"
      method = "Check"
      body = { service = "" }
    }

    transport {
      proxy_profile = "corp"
    }
  }

  result {
    output = "ok"
  }
}
"#;
    fs::write(local_dir.join("invalid_grpc_proxy.hcl"), hcl).unwrap();

    let err = validate_all_from_dirs(&global_dir, &local_dir).unwrap_err();
    let rendered = format!("{err:#}");
    assert!(
        rendered.contains("grpc transport.proxy_profile is not supported"),
        "unexpected error: {rendered}"
    );
}
