// el-secrets — Secrets management for el-ui.
//
// A Secret is a value with a redacted display contract. Source plugins
// fetch by key from env, Vault, AWS Secrets Manager, or in-memory.
// `SecretsResolver` composes a chain and resolves required keys at startup.

// ── Errors ───────────────────────────────────────────────────────────────────

let SECRET_ERR_MISSING:    String = "secret.missing"
let SECRET_ERR_SOURCE:     String = "secret.source_error"
let SECRET_ERR_PERMISSION: String = "secret.permission_denied"

// ── Source kinds ─────────────────────────────────────────────────────────────

let SRC_ENV:          String = "env"
let SRC_VAULT:        String = "vault"
let SRC_AWS_SECRETS:  String = "aws_secrets"
let SRC_IN_MEMORY:    String = "in_memory"

// ── Source descriptor ───────────────────────────────────────────────────────

type SecretsSource {
    kind:        String   // env | vault | aws_secrets | in_memory
    config_json: String   // source-specific (e.g. {"prefix":"EL_SECRET_"} or {"path":"secret/data/app"})
}

fn source_env(prefix: String) -> SecretsSource {
    { "kind": "env", "config_json": "{\"prefix\":\"" + prefix + "\"}" }
}

fn source_vault(addr: String, token: String, mount: String) -> SecretsSource {
    let cfg: String = "{\"addr\":\"" + addr + "\",\"token\":\"" + token
        + "\",\"mount\":\"" + mount + "\"}"
    { "kind": "vault", "config_json": cfg }
}

fn source_aws(region: String) -> SecretsSource {
    { "kind": "aws_secrets", "config_json": "{\"region\":\"" + region + "\"}" }
}

fn source_in_memory() -> SecretsSource {
    { "kind": "in_memory", "config_json": "{\"map\":{}}" }
}

// ── Source lookup ───────────────────────────────────────────────────────────
//
// Each source kind has its own fetch fn. `source_get` dispatches by kind.

fn source_get(src: SecretsSource, key: String) -> String {
    if str_eq(src.kind, "env") {
        let prefix: String = json_get(src.config_json, "prefix")
        return env(prefix + str_to_upper(str_replace(key, ".", "_")))
    }
    if str_eq(src.kind, "vault") {
        let addr:  String = json_get(src.config_json, "addr")
        let token: String = json_get(src.config_json, "token")
        let mount: String = json_get(src.config_json, "mount")
        return vault_kv_get_at(addr, token, mount, key)
    }
    if str_eq(src.kind, "aws_secrets") {
        let region: String = json_get(src.config_json, "region")
        return aws_secrets_get(region, key)
    }
    if str_eq(src.kind, "in_memory") {
        let map: String = json_get(src.config_json, "map")
        return json_get(map, key)
    }
    ""
}

fn in_memory_insert(src: SecretsSource, key: String, value: String) -> SecretsSource {
    let map: String = json_get(src.config_json, "map")
    let map = json_set(map, key, "\"" + value + "\"")
    let cfg = json_set(src.config_json, "map", map)
    { "kind": src.kind, "config_json": cfg }
}

// ── Secret<T> — redacted wrapper ────────────────────────────────────────────
//
// A Secret stores its raw value behind a tag. `secret_display` always
// returns `[REDACTED]`. `secret_expose` returns the actual string.
// Serializers should call `secret_display` by default.

type Secret {
    tag: String   // marker: "secret"
    raw: String   // the actual value — never log this directly
}

fn secret_new(value: String) -> Secret {
    { "tag": "secret", "raw": value }
}

fn secret_display(s: Secret) -> String {
    "[REDACTED]"
}

fn secret_expose(s: Secret) -> String {
    s.raw
}

// Disposable read-once guard — like Rust's `SecretGuard`. Consumes the secret
// once and zeroes it. Useful at boundaries: read JWT signing key once, hand to
// the signing routine, never readable again.
type SecretGuard {
    consumed: Bool
    raw:      String
}

fn guard_new(s: Secret) -> SecretGuard {
    { "consumed": false, "raw": s.raw }
}

fn guard_take(g: SecretGuard) -> String {
    if g.consumed { return "" }
    g.raw
}

// ── SecretsResolver — builder ───────────────────────────────────────────────
//
// Sources are tried in order. First non-empty hit wins.
// `require(key)` marks a key as required at startup; `resolve()` fails if any
// required key is unresolved.

type SecretsResolver {
    sources_json:  String   // JSON array of SecretsSource
    required_json: String   // JSON array of required keys
}

fn resolver_new() -> SecretsResolver {
    { "sources_json": "[]", "required_json": "[]" }
}

fn resolver_source(r: SecretsResolver, src: SecretsSource) -> SecretsResolver {
    { "sources_json": json_array_push(r.sources_json, json_encode(src)),
      "required_json": r.required_json }
}

fn resolver_require(r: SecretsResolver, key: String) -> SecretsResolver {
    { "sources_json": r.sources_json,
      "required_json": json_array_push(r.required_json, "\"" + key + "\"") }
}

// Resolved bag. Keys -> Secret. Failure if any required key is missing.
type ResolvedSecrets {
    map_json: String   // JSON: key -> raw value
    errors:   String   // JSON array of missing required keys
}

fn resolver_resolve(r: SecretsResolver) -> ResolvedSecrets {
    let resolved: String = "{}"
    let errors:   String = "[]"
    let n: Int = json_array_len(r.required_json)
    let i: Int = 0
    while i < n {
        let key: String = json_array_get(r.required_json, i)
        let value: String = lookup_chain(r.sources_json, key)
        if str_eq(value, "") {
            let errors = json_array_push(errors, "\"" + key + "\"")
        }
        if !str_eq(value, "") {
            let resolved = json_set(resolved, key, "\"" + value + "\"")
        }
        let i = i + 1
    }
    { "map_json": resolved, "errors": errors }
}

fn lookup_chain(sources_json: String, key: String) -> String {
    let n: Int = json_array_len(sources_json)
    let i: Int = 0
    while i < n {
        let src_json: String = json_array_get(sources_json, i)
        let src: SecretsSource = { "kind": json_get(src_json, "kind"),
                                   "config_json": json_get(src_json, "config_json") }
        let v: String = source_get(src, key)
        if !str_eq(v, "") { return v }
        let i = i + 1
    }
    ""
}

fn resolved_require(r: ResolvedSecrets, key: String) -> Secret {
    let raw: String = json_get(r.map_json, key)
    secret_new(raw)
}

// ── Entry — smoke test ──────────────────────────────────────────────────────

let src: SecretsSource = source_in_memory()
let src = in_memory_insert(src, "jwt.key", "test-key-for-testing-only")
let resolver: SecretsResolver = resolver_source(resolver_new(), src)
let resolver = resolver_require(resolver, "jwt.key")
let resolved: ResolvedSecrets = resolver_resolve(resolver)
let jwt: Secret = resolved_require(resolved, "jwt.key")
println("[el-secrets] jwt.key = " + secret_display(jwt))
