"""Lab client for the Offensive AI toy support app.

Runs one real request per subcommand and prints the JSON the app returned, so
every step in src/data/offensive-ai-labs.ts points at something observable.
Standard library only; no curl required.
"""

import argparse
import json
import sys
from pathlib import Path

from _client import call, tenant_key


def _print(status, payload):
    print(json.dumps({"http_status": status, **payload}, indent=2))


def _cmd_health(args):
    _print(*call("GET", "/healthz"))


def _cmd_tenants(args):
    _print(*call("GET", "/tenants"))


def _cmd_retrieve(args):
    body = {"tenant": args.tenant, "query": args.query, "acl": "off" if args.acl_off else "on"}
    _print(*call("POST", "/retrieve", body))


def _cmd_ask(args):
    body = {"tenant": args.tenant, "query": args.query, "prompt": args.prompt}
    _print(*call("POST", "/ask", body))


def _cmd_tools_list(args):
    _print(*call("GET", "/tools"))


def _cmd_tools_call(args):
    parsed_args = {}
    for pair in args.arg or []:
        key, _, value = pair.partition("=")
        parsed_args[key] = value
    body = {"tenant": args.tenant, "tool": args.tool, "args": parsed_args, "approved": args.approved}
    _print(*call("POST", "/tools/call", body))


def _cmd_tools_audit(args):
    _print(*call("GET", "/tools/audit"))


def _cmd_memory_get(args):
    _print(*call("GET", f"/memory?tenant={args.tenant}"))


def _cmd_memory_set(args):
    _print(*call("POST", "/memory", {"tenant": args.tenant, "content": args.content, "source": args.source}))


def _cmd_memory_reset(args):
    _print(*call("DELETE", f"/memory?tenant={args.tenant}"))


def _cmd_gateway_chat(args):
    body = {"prompt": args.prompt, "force_fallback": args.force_fallback}
    headers = {"x-api-key": tenant_key(args.tenant)}
    for _ in range(max(1, args.repeat)):
        status, payload = call("POST", "/gateway/chat", body, headers=headers)
        _print(status, payload)
        if status == 429:
            break


def _cmd_gateway_logs(args):
    actor = args.as_tenant or args.tenant
    _print(*call("GET", f"/gateway/logs?tenant={args.tenant}", headers={"x-api-key": tenant_key(actor)}))


def _cmd_gateway_quota(args):
    _print(*call("GET", "/gateway/quota", headers={"x-api-key": tenant_key(args.tenant)}))


def _cmd_incidents(args):
    _print(*call("GET", "/incidents"))


def _cmd_incident_timeline(args):
    _print(*call("GET", f"/incidents/{args.id}/timeline"))


def _cmd_incident_contain(args):
    _print(*call("POST", f"/incidents/{args.id}/contain"))


def _cmd_evidence_export(args):
    status, payload = call("GET", "/evidence/export")
    if args.out:
        with open(args.out, "w", encoding="utf-8") as handle:
            json.dump(payload, handle, indent=2)
        print(json.dumps({"http_status": status, "written_to": args.out}, indent=2))
    else:
        _print(status, payload)


def _expected_health_marker():
    """Read the deterministic health marker from the fixture contract."""
    contract = Path(__file__).resolve().parent.parent / "fixtures" / "evals" / "markers.json"
    with contract.open("r", encoding="utf-8") as handle:
        return json.load(handle)["markers"]["health"]


def _cmd_eval(args):
    """Eval gate. Exit codes (see fixtures/evals/ci-contract.json):

    0 = PASS (gate passed and the /healthz marker matched the contract),
    1 = FAIL (gate failed or marker mismatched — release blocked),
    2 = ERROR (app unreachable, malformed response, or unreadable pack).

    --ci also asserts the /healthz marker against fixtures/evals/markers.json
    and fails closed on mismatch. Without --ci the JSON is printed and no
    exit code is implied.
    """
    try:
        status, payload = call("POST", f"/evals/run?prompt={args.prompt}")
    except Exception as exc:  # noqa: BLE001 - fail closed with exit 2
        print(f"[lab] CI ERROR: eval request failed: {exc}", file=sys.stderr)
        raise SystemExit(2)
    if status != 200 or "gate" not in payload:
        print(f"[lab] CI ERROR: unexpected eval response (http {status}): {payload}", file=sys.stderr)
        raise SystemExit(2)
    _print(status, payload)
    if not args.ci:
        return
    try:
        expected_marker = _expected_health_marker()
    except Exception as exc:  # noqa: BLE001 - fail closed with exit 2
        print(f"[lab] CI ERROR: cannot read marker contract: {exc}", file=sys.stderr)
        raise SystemExit(2)
    try:
        health_status, health = call("GET", "/healthz")
    except Exception as exc:  # noqa: BLE001 - fail closed with exit 2
        print(f"[lab] CI ERROR: health request failed: {exc}", file=sys.stderr)
        raise SystemExit(2)
    marker = health.get("marker")
    if health_status != 200 or marker != expected_marker:
        print(f"[lab] CI gate FAIL: health marker {marker!r} != expected {expected_marker!r}", file=sys.stderr)
        raise SystemExit(1)
    if payload.get("gate") == "fail":
        print(f"[lab] CI gate FAIL: {payload.get('cases_failed')} case(s) failed", file=sys.stderr)
        raise SystemExit(1)
    print("[lab] CI gate PASS", file=sys.stderr)


def _cmd_reset(args):
    _print(*call("POST", "/lab/reset"))


def build_parser():
    parser = argparse.ArgumentParser(description="Offensive AI toy-app lab client")
    sub = parser.add_subparsers(dest="command", required=True)

    sub.add_parser("health").set_defaults(func=_cmd_health)
    sub.add_parser("tenants").set_defaults(func=_cmd_tenants)

    p = sub.add_parser("retrieve")
    p.add_argument("--tenant", required=True)
    p.add_argument("--query", required=True)
    p.add_argument("--acl-off", action="store_true")
    p.set_defaults(func=_cmd_retrieve)

    p = sub.add_parser("ask")
    p.add_argument("--tenant", required=True)
    p.add_argument("--query", required=True)
    p.add_argument("--prompt", choices=["fixed", "weak"], default="fixed")
    p.set_defaults(func=_cmd_ask)

    sub.add_parser("tools-list").set_defaults(func=_cmd_tools_list)

    p = sub.add_parser("tools-call")
    p.add_argument("--tenant", required=True)
    p.add_argument("--tool", required=True)
    p.add_argument("--arg", action="append")
    p.add_argument("--approved", action="store_true")
    p.set_defaults(func=_cmd_tools_call)

    sub.add_parser("tools-audit").set_defaults(func=_cmd_tools_audit)

    p = sub.add_parser("memory-get")
    p.add_argument("--tenant", required=True)
    p.set_defaults(func=_cmd_memory_get)

    p = sub.add_parser("memory-set")
    p.add_argument("--tenant", required=True)
    p.add_argument("--content", required=True)
    p.add_argument("--source", default="operator")
    p.set_defaults(func=_cmd_memory_set)

    p = sub.add_parser("memory-reset")
    p.add_argument("--tenant", required=True)
    p.set_defaults(func=_cmd_memory_reset)

    p = sub.add_parser("gateway-chat")
    p.add_argument("--tenant", required=True)
    p.add_argument("--prompt", required=True)
    p.add_argument("--force-fallback", action="store_true")
    p.add_argument("--repeat", type=int, default=1, help="send the request N times (stops early on 429)")
    p.set_defaults(func=_cmd_gateway_chat)

    p = sub.add_parser("gateway-logs")
    p.add_argument("--tenant", required=True)
    p.add_argument("--as-tenant")
    p.set_defaults(func=_cmd_gateway_logs)

    p = sub.add_parser("gateway-quota")
    p.add_argument("--tenant", required=True)
    p.set_defaults(func=_cmd_gateway_quota)

    sub.add_parser("incidents").set_defaults(func=_cmd_incidents)

    p = sub.add_parser("incident-timeline")
    p.add_argument("--id", required=True)
    p.set_defaults(func=_cmd_incident_timeline)

    p = sub.add_parser("incident-contain")
    p.add_argument("--id", required=True)
    p.set_defaults(func=_cmd_incident_contain)

    p = sub.add_parser("evidence-export")
    p.add_argument("--out")
    p.set_defaults(func=_cmd_evidence_export)

    p = sub.add_parser(
        "eval",
        epilog="CI contract (fixtures/evals/ci-contract.json): exit 0 = PASS, "
        "1 = FAIL (gate or marker mismatch), 2 = infrastructure error. "
        "The deliberate failing case is `eval --ci --prompt weak` "
        "(eval-rag-injection-001 follows the poisoned doc).",
    )
    p.add_argument("--prompt", choices=["fixed", "weak"], default="fixed")
    p.add_argument(
        "--ci",
        action="store_true",
        help="enforce the CI gate: exit 0 on PASS, 1 on FAIL, 2 on infrastructure error",
    )
    p.set_defaults(func=_cmd_eval)

    sub.add_parser("reset").set_defaults(func=_cmd_reset)
    return parser


def main():
    args = build_parser().parse_args()
    args.func(args)


if __name__ == "__main__":
    main()
