#!/usr/bin/env python3
# Author: Tom Sapletta · https://tom.sapletta.com
# Part of the ifURI solution.
#
# Use EVERY urirun connector through YAML flows:
#   1. generate + run flows/install.flow.yaml  -> pip install -e each connector
#   2. build one registry from all their bindings
#   3. generate + run flows/smoke.flow.yaml     -> call a representative route per
#      connector (config-gated ones are checked for "importable" instead)
#   4. report which connectors installed, validated and actually ran.
#
# The two YAML files are written to flows/ so you can read/run them directly.

from __future__ import annotations

import argparse
import importlib
import json
import os
import subprocess
import sys

HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.normpath(os.path.join(HERE, "..", ".."))


def _prepend_pythonpath(paths: list[str]) -> None:
    existing = [p for p in os.environ.get("PYTHONPATH", "").split(os.pathsep) if p]
    prepend = [os.path.abspath(p) for p in paths if p and os.path.isdir(p)]
    merged = list(dict.fromkeys([*prepend, *existing]))
    os.environ["PYTHONPATH"] = os.pathsep.join(merged)
    for path in reversed(prepend):
        if path not in sys.path:
            sys.path.insert(0, path)


def _ensure_imports() -> None:
    cand = os.path.join(ROOT, "urirun", "adapters", "python")
    _prepend_pythonpath([cand])


_ensure_imports()
import urirun  # noqa: E402
import yaml  # noqa: E402

sys.path.insert(0, HERE)
import pkg_connector  # noqa: E402
from connectors import CONNECTORS  # noqa: E402


def _ensure_connector_imports() -> None:
    """Make local editable connectors visible to isolated handler subprocesses."""
    paths = [os.path.join(ROOT, "urirun", "adapters", "python")]
    for name in CONNECTORS:
        cdir = os.path.join(ROOT, f"urirun-connector-{name}")
        paths.append(cdir)
        paths.append(os.path.join(cdir, "src"))
    _prepend_pythonpath(paths)


def write_flow(path: str, task: str, allow: list[str], steps: list[dict]) -> dict:
    flow = {"task": {"title": task}, "allow": allow, "steps": steps}
    with open(path, "w", encoding="utf-8") as fh:
        fh.write("# generated by run.py — a ready urirun flow you can read and run\n")
        fh.write(yaml.safe_dump(flow, sort_keys=False, allow_unicode=True))
    return flow


def run_flow(registry: dict, steps: list[dict]) -> list[dict]:
    # urirun.run_steps does the per-step run + per-scheme policy + result unwrap
    # (incl. argv stdout JSON) that this example used to spell out by hand.
    return urirun.run_steps(steps, registry, execute=True, stop_on_error=False)


def merged_registry() -> tuple[dict, dict]:
    """Compile one registry from pkg:// + every (importable) connector's bindings."""
    _ensure_connector_imports()
    doc = {"version": "urirun.bindings.v2", "bindings": dict(pkg_connector.bindings()["bindings"])}
    present = {}
    for name, (module, *_rest) in CONNECTORS.items():
        try:
            mod = importlib.import_module(module)
            doc["bindings"].update(mod.urirun_bindings()["bindings"])
            present[name] = True
        except Exception:  # noqa: BLE001 - a connector that won't import is reported below
            present[name] = False
    return urirun.compile_registry(doc), present


def main(argv: list[str] | None = None) -> int:
    p = argparse.ArgumentParser(prog="run", description="Install + use every urirun connector via YAML flows")
    p.add_argument("--no-install", action="store_true", help="skip the install flow (use what's already installed)")
    p.add_argument("--json", action="store_true")
    args = p.parse_args(argv)
    _ensure_connector_imports()

    # 1) the INSTALL flow — installing each connector is itself a URI step
    install_steps = [{"id": f"install-{n}", "uri": "pkg://host/connector/command/install", "payload": {"name": n}}
                     for n in CONNECTORS]
    write_flow(os.path.join(HERE, "flows", "install.flow.yaml"),
               "Install every urirun connector (pip install -e)", ["pkg://*"], install_steps)

    pkg_reg = urirun.compile_registry(pkg_connector.bindings())
    install_results = {}
    if not args.no_install:
        res = run_flow(pkg_reg, install_steps)
        install_results = {s["payload"]["name"]: r["ok"] for s, r in zip(install_steps, res)}
        importlib.invalidate_caches()  # see connectors the install flow just added

    # 2) one registry from all connectors now present
    registry, present = merged_registry()
    allowed = {route["uri"] for route in urirun.action_space(registry)}

    # 3) the SMOKE flow — a representative route per connector (or an "installed?" check)
    smoke_steps = []
    for name, (module, route, payload, note) in CONNECTORS.items():
        if route and route in allowed:
            smoke_steps.append({"id": f"use-{name}", "uri": route, "payload": payload})
        else:  # config-gated: prove the module is importable instead of running it
            smoke_steps.append({"id": f"check-{name}", "uri": "pkg://host/connector/query/installed", "payload": {"module": module}})
    write_flow(os.path.join(HERE, "flows", "smoke.flow.yaml"),
               "Use every urirun connector (one route each)",
               sorted({s["uri"].split("://")[0] + "://*" for s in smoke_steps}), smoke_steps)

    smoke = run_flow(registry, smoke_steps)

    # 4) report
    rows = []
    for (name, (module, route, payload, note)), res in zip(CONNECTORS.items(), smoke):
        gated = route is None
        failure = res.get("data") if not res.get("ok") else None
        status = ("RAN ✓" if res["ok"] else f"FAILED: {json.dumps(failure, ensure_ascii=False, default=str)[:240]}") if not gated \
            else ("installed + valid (config-gated)" if res["ok"] else "NOT IMPORTABLE")
        rows.append({"connector": name, "installed": present.get(name, False),
                     "gated": gated, "ok": res["ok"], "status": status, "note": note,
                     "failure": failure})

    if args.json:
        print(json.dumps({"install": install_results, "smoke": rows}, ensure_ascii=False, indent=2))
    else:
        print(f"\n{len(CONNECTORS)} connectors — install via flows/install.flow.yaml, use via flows/smoke.flow.yaml\n")
        print(f"  {'connector':<18}{'installed':<11}{'status'}")
        print("  " + "-" * 64)
        for r in rows:
            print(f"  {r['connector']:<18}{('yes' if r['installed'] else 'no'):<11}{r['status']}  ({r['note']})")
        ran = sum(1 for r in rows if not r["gated"] and r["ok"])
        gated_ok = sum(1 for r in rows if r["gated"] and r["ok"])
        broken = sum(1 for r in rows if not r["ok"])
        print("  " + "-" * 64)
        print(f"  {ran} ran a route · {gated_ok} installed+valid (config-gated) · {broken} broken")
    return 0 if all(r["ok"] for r in rows) else 1


if __name__ == "__main__":
    raise SystemExit(main())
