#!/usr/bin/env python3
"""Campaign 10 — deploy apps through the REAL endpoints.

Mirrors the controller's own generator semantics (internal/stacks/deploy.go:870 generateValue):
  password:N -> N random alphanumerics
  hex:N      -> N random BYTES, hex-encoded (2N chars)
  base64key:N-> "base64:" + base64(N random bytes)
  static:X   -> X
Runs on DooPlex; talks to the guest via ssh -J demo-hp + pct exec + /root/c10api.sh.
Secrets are written to a 0600 manifest, never printed.
"""
import base64, json, os, secrets, string, subprocess, sys

APPS = sys.argv[1:] or ["rallly", "homebox", "grafana", "papra"]
DOMAIN = "c10.felhom.eu"
HOME = os.path.expanduser("~/.config/campaign10")
HOSTPW = json.load(open(os.path.join(HOME, "host-recovery.json")))["password"]


def ssh(cmd, timeout=900):
    full = ["sshpass", "-e", "ssh", "-o", "StrictHostKeyChecking=no",
            "-o", "ConnectTimeout=25", "-J", "demo-hp", "root@192.168.0.105",
            "LC_ALL=C " + cmd]
    env = dict(os.environ, SSHPASS=HOSTPW, LC_ALL="C")
    return subprocess.run(full, capture_output=True, text=True, timeout=timeout, env=env).stdout


def api(method, path, body=None):
    if body is None:
        return ssh(f"pct exec 9201 -- /root/c10api.sh {method} {path} 2>/dev/null")
    b64 = base64.b64encode(json.dumps(body).encode()).decode()
    # body travels base64 so no quoting/locale can mangle a secret
    return ssh("pct exec 9201 -- bash -c \"echo %s | base64 -d > /tmp/.b && "
               "/root/c10api.sh %s %s \\\"\\$(cat /tmp/.b)\\\" --json; shred -u /tmp/.b\" 2>/dev/null"
               % (b64, method, path))


def gen(spec):
    kind, _, param = spec.partition(":")
    if kind == "password":
        al = string.ascii_letters + string.digits
        return "".join(secrets.choice(al) for _ in range(int(param)))
    if kind == "hex":
        return secrets.token_bytes(int(param)).hex()
    if kind == "base64key":
        return "base64:" + base64.b64encode(secrets.token_bytes(int(param))).decode()
    if kind == "static":
        return param
    raise ValueError("unknown generator %r" % spec)


manifest_path = os.path.join(HOME, "app-secrets.json")
manifest = json.load(open(manifest_path)) if os.path.exists(manifest_path) else {}

for app in APPS:
    raw = api("GET", f"/api/stacks/{app}/deploy-fields")
    try:
        meta = json.loads(raw[raw.index("{"):raw.rindex("}") + 1])["data"]["metadata"]
    except Exception as e:
        print(f"{app:12s} FIELDS-FAIL {e} :: {raw[:160]!r}")
        continue

    values, classes = {}, {}
    for f in meta.get("deploy_fields", []):
        ev, typ, spec, dflt = f["env_var"], f.get("type", ""), f.get("generate", ""), f.get("default", "")
        if typ == "domain":
            values[ev] = DOMAIN
        elif spec:
            values[ev] = gen(spec)
        elif dflt:
            values[ev] = dflt
        elif f.get("required"):
            print(f"{app:12s} WARN required field {ev} has no default/generator")
        if typ in ("secret", "password"):
            classes[ev] = {"type": typ, "data_key": bool(f.get("data_key")), "label": f.get("label", "")}

    manifest[app] = {"values": values, "secret_classes": classes,
                     "subdomain": values.get("SUBDOMAIN", ""), "domain": DOMAIN}

    out = api("POST", f"/api/stacks/{app}/deploy", {"values": values})
    ok = '"ok":true' in out
    print(f"{app:12s} deploy -> {'OK' if ok else 'FAIL'}  "
          f"secret={sum(1 for c in classes.values() if c['type']=='secret')} "
          f"password={sum(1 for c in classes.values() if c['type']=='password')} "
          f"data_key={sum(1 for c in classes.values() if c['data_key'])}")
    if not ok:
        print("   ", out.strip()[:300])

with open(manifest_path, "w") as fh:
    json.dump(manifest, fh, indent=1)
os.chmod(manifest_path, 0o600)
print("\nsecrets + secret-class map written to", manifest_path, "(0600, not printed)")
