#!/usr/bin/env python3
"""
fix-bundle-add-memcached.py   (BUNDLEFIX-004, part 2)

Adds the `memcached` application AND the
`nova-cloud-controller:memcache <-> memcached:cache` relation to the Caracal
bundle, matching the live `juju deploy memcached` + `juju integrate` already
applied to the running model.

Why: nova-cloud-controller treats `memcache` as a required relation. The Caracal
rebuild omitted memcached entirely, so a fresh `juju deploy` of the bundle would
leave nova-cc blocked on "Missing relations: memcache" (no instance scheduling).

App block added (placement to: [lxd:8] = openstack0, where it landed live; metal
space; latest/stable, the only stable channel for the memcached charm):

  memcached:
    charm: memcached
    channel: latest/stable
    num_units: 1
    to: [lxd:8]
    bindings: *internal-bindings
    constraints: arch=amd64

Relation added:
  - [nova-cloud-controller:memcache, memcached:cache]

Safe by construction: line edits (preserve anchors/comments/formatting),
timestamped .bak, unified diff, idempotent, yaml.safe_load verification.

Usage: python3 fix-bundle-add-memcached.py [path/to/bundle.yaml]   (default ./bundle.yaml)
"""
import sys, os, difflib, datetime

DEFAULT = "bundle.yaml"

APP_BLOCK = [
    "",
    "  # memcached: nova-cloud-controller token/cell caching (BUNDLEFIX-004)",
    "  memcached:",
    "    charm: memcached",
    "    channel: latest/stable",
    "    num_units: 1",
    "    to: [lxd:8]",
    "    bindings: *internal-bindings",
    "    constraints: arch=amd64",
    "",
]
RELATION_LINE = "  - [nova-cloud-controller:memcache, memcached:cache]"


def main():
    path = sys.argv[1] if len(sys.argv) > 1 else DEFAULT
    if not os.path.isfile(path):
        print(f"[ABORT] not found: {path}")
        return 2

    original = open(path, encoding="utf-8").read()
    lines = original.splitlines()

    have_app = any(l.strip().startswith("memcached:") for l in lines)
    have_rel = "memcached:cache" in original
    if have_app and have_rel:
        print("[OK/IDEMPOTENT] memcached app and relation already present; no change.")
        return 0
    if have_app != have_rel:
        print(f"[ABORT] partial state (app={have_app}, relation={have_rel}); fix by hand to avoid duplication.")
        return 3

    # Bundle order here is description -> variables -> machines -> applications -> relations,
    # so `relations:` is the END of the applications section. Anchor BOTH inserts to it:
    # the app block goes immediately before `relations:` (last app), the relation immediately after.
    rel_idx = next((i for i, l in enumerate(lines) if l.rstrip() == "relations:"), None)
    if rel_idx is None:
        print("[ABORT] could not find top-level 'relations:' key.")
        return 4

    out = []
    for i, l in enumerate(lines):
        if i == rel_idx:
            out.extend(APP_BLOCK)        # app block: end of applications (just before relations:)
        out.append(l)
        if i == rel_idx:
            out.append(RELATION_LINE)    # relation: first entry after relations:
    new = "\n".join(out) + ("\n" if original.endswith("\n") else "")

    print("=== unified diff ===")
    print("\n".join(difflib.unified_diff(
        original.splitlines(), new.splitlines(),
        fromfile=f"{path} (orig)", tofile=f"{path} (new)", lineterm="")))

    try:
        import yaml
        d = yaml.safe_load(new)
        a = d["applications"]
        rels = d.get("relations", [])
        assert "memcached" in a, "memcached app missing after edit"
        assert a["memcached"].get("charm") == "memcached", "charm != memcached"
        assert a["memcached"].get("bindings") == {"": "metal"}, f"bindings={a['memcached'].get('bindings')}"
        mc = [r for r in rels if any("memcache" in str(x) for x in r)]
        assert mc, "memcache relation missing after edit"
        print(f"[VERIFY] OK: memcached app present, bindings {{'': 'metal'}}, relation {mc}")
        print(f"[VERIFY] totals now: apps={len(a)} relations={len(rels)}")
    except ImportError:
        print("[WARN] PyYAML missing; skipped semantic verify (re-verify on jumphost after pull).")
    except Exception as e:
        print(f"[ABORT] verification failed: {e}")
        return 5

    ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
    bak = f"{path}.bak-{ts}"
    open(bak, "w", encoding="utf-8").write(original)
    open(path, "w", encoding="utf-8").write(new)
    print(f"[WROTE] {path}  (backup: {bak})")
    return 0


if __name__ == "__main__":
    sys.exit(main())
