#!/usr/bin/env python3
"""
run_proof_of_life.py — AEGIS Local AI Operator
Proof-of-Life Gate: 13 checks that must ALL pass before Prompt 1 is complete.
Runs standalone, writes PROOF_OF_LIFE_REPORT.md, exits 0 on pass / 1 on fail.
/usr/bin/python3 stdlib only.
"""

import sys
import os
import subprocess
import threading
import time
import json
from datetime import datetime

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

REPORT_PATH = os.path.join(os.path.dirname(__file__), "PROOF_OF_LIFE_REPORT.md")
APP_PORT     = 7779   # dedicated port for proof-of-life (avoids conflicts with 7777/7778)
HOST         = "127.0.0.1"

# ---------------------------------------------------------------------------
# Result tracker
# ---------------------------------------------------------------------------

results = []

def check(number: int, name: str, fn):
    """Run a single proof-of-life check. Append result to results list."""
    try:
        detail = fn()
        status = "PASS"
        error  = None
    except Exception as e:
        import traceback
        status = "FAIL"
        detail = None
        error  = f"{type(e).__name__}: {e}"
        traceback.print_exc()

    results.append({
        "number": number,
        "name":   name,
        "status": status,
        "detail": detail,
        "error":  error,
    })
    icon = "✅" if status == "PASS" else "❌"
    print(f"  {icon} [{number:02d}] {name}: {status}")
    if error:
        print(f"         ERROR: {error}")
    return status == "PASS"


# ---------------------------------------------------------------------------
# Individual checks
# ---------------------------------------------------------------------------

def check_01_task_compiler():
    """Task compiler accepts text, returns structured task."""
    from execution_kernel.task_compiler import compile_task
    t = compile_task("Generate a status report for the memory stack")
    assert t["task_type"]  == "report",  f"Expected report, got {t['task_type']}"
    assert t["risk_level"] == "safe",    f"Expected safe, got {t['risk_level']}"
    assert t["task_id"],                 "task_id missing"
    assert t["created_at"],              "created_at missing"
    assert not t["approved"],            "should default to not approved"
    # Test blocked type
    t2 = compile_task("Send an email to the team")
    assert t2["task_type"]  == "message","Expected message"
    assert t2["risk_level"] == "blocked","Expected blocked"
    return f"compile_task OK — report→safe, message→blocked"


def check_02_safety_gate():
    """Safety gate blocks dangerous inputs, allows safe ones."""
    from execution_kernel.task_compiler import compile_task
    from execution_kernel.safety_gate   import evaluate, approve_task

    t_safe = compile_task("Audit Postgres connections")
    r_safe = evaluate(t_safe)
    assert r_safe["gate_verdict"] == "PASS",    f"Audit should PASS: {r_safe}"
    assert r_safe["allowed"],                    "Audit should be allowed"

    t_bad = compile_task("Send an email to everyone")
    r_bad = evaluate(t_bad)
    assert r_bad["gate_verdict"] == "BLOCKED",  f"Email should be BLOCKED: {r_bad}"
    assert not r_bad["allowed"],                 "Email must not be allowed"

    t_del = compile_task("Delete old records")
    r_del = evaluate(t_del)
    assert r_del["gate_verdict"] in ("NEEDS_APPROVAL", "BLOCKED"), \
        f"Delete should need approval: {r_del}"

    return f"safety_gate OK — audit→PASS, email→BLOCKED, delete→{r_del['gate_verdict']}"


def check_03_safe_action_matrix():
    """Safe action matrix returns allowed/blocked action list."""
    from execution_kernel.safe_action_matrix import (
        get_matrix, is_action_allowed, is_action_blocked, get_task_type_risk
    )
    m = get_matrix()
    assert len(m["safe"])          > 10, "Too few safe actions"
    assert len(m["needs_approval"]) > 5, "Too few approval actions"
    assert len(m["blocked"])        > 5, "Too few blocked actions"
    assert is_action_allowed("generate_report"),  "generate_report should be safe"
    assert is_action_blocked("send_email"),        "send_email should be blocked"
    assert get_task_type_risk("report") == "safe", "report type should be safe"
    return (f"matrix OK — {len(m['safe'])} safe, "
            f"{len(m['needs_approval'])} approval, "
            f"{len(m['blocked'])} blocked")


def check_04_tool_router():
    """Tool router routes task type to correct handler."""
    from execution_kernel.task_compiler import compile_task
    from execution_kernel.tool_router   import route, list_routes

    t = compile_task("Generate a weekly status report")
    r = route(t)
    assert r["routed"],                 "Should be routed"
    assert r["handler"],                "Handler must be set"
    assert r["task_type"] == "report",  f"Expected report, got {r['task_type']}"
    assert r["module"],                 "Module must be set"

    routes = list_routes()
    assert len(routes) >= 10,           f"Expected ≥10 routes, got {len(routes)}"
    return f"tool_router OK — {r['handler']} via {r['module']} ({len(routes)} routes)"


def check_05_execution_kernel_v4():
    """Execution Kernel V4 runs a task end-to-end."""
    from execution_kernel.kernel import run, kernel_status

    # Safe task
    r = run("Generate a status report for the memory stack")
    assert r["status"] == "completed",    f"Expected completed: {r}"
    assert r["output"],                   "Output must not be empty"
    # report tasks now use the real file-backed handler (no [DEMO]);
    # verify output is non-empty and contains expected real-handler marker OR legacy demo.
    assert (r["output"].startswith("Report saved:") or "[DEMO]" in r["output"]), \
        f"Output should be real report path or demo marker: {r['output']}"
    assert r["pipeline"] == ["compile", "gate", "route", "execute"], \
        f"Pipeline wrong: {r['pipeline']}"

    # Blocked task
    r2 = run("Send an email to the team")
    assert r2["status"] == "blocked",     f"Expected blocked: {r2}"

    # Kernel status
    ks = kernel_status()
    assert ks["kernel_version"] == "V4",  "Must be V4"
    assert ks["status"] == "operational", "Must be operational"

    return (f"kernel V4 OK — compile→gate→route→execute; "
            f"{ks['safe_actions']} safe, {ks['blocked_actions']} blocked actions")


def check_06_brain_council():
    """Brain Council returns 3-brain committee response."""
    from execution_kernel.task_compiler import compile_task
    from execution_kernel.brain_council  import consult

    t = compile_task("Generate a weekly status report")
    r = consult(t)
    assert r["quorum"],                      f"Report task should have quorum: {r}"
    assert len(r["council"]) == 3,           "Need exactly 3 brains"
    assert r["session_id"],                  "session_id must be set"
    for brain in r["council"]:
        assert brain["brain"] in ("Analyst", "Strategist", "Skeptic")
        assert brain["response"],            f"Brain {brain['brain']} needs a response"
        assert brain["recommendation"] in ("proceed", "review", "hold")

    names = [b["brain"] for b in r["council"]]
    return (f"brain_council OK — {names} | quorum={r['quorum']} "
            f"({r['proceed_votes']}/3 proceed)")


def check_07_challenge_mode():
    """Challenge Mode returns challenge questions and counter-argument."""
    from execution_kernel.task_compiler  import compile_task
    from execution_kernel.challenge_mode import challenge, get_challenges_for_type

    t = compile_task("Generate a weekly status report")
    r = challenge(t)
    assert r["challenge_id"],            "challenge_id must be set"
    assert len(r["questions"]) >= 3,     f"Need ≥3 questions, got {len(r['questions'])}"
    assert r["counter_argument"],        "counter_argument must be set"
    assert r["verdict"] in ("proceed_with_caution", "review_recommended")

    # Check by type
    qs = get_challenges_for_type("audit")
    assert len(qs) >= 3,                 "Audit needs ≥3 questions"

    return (f"challenge_mode OK — {r['challenge_count']} questions, "
            f"verdict={r['verdict']}")


def check_08_brain_bridge():
    """Brain Bridge returns cross-brain templates."""
    from execution_kernel.task_compiler import compile_task
    from execution_kernel.brain_bridge   import build_message, list_templates

    t = compile_task("Generate a weekly status report")
    templates = list_templates()
    assert len(templates) >= 4, f"Need ≥4 templates, got {len(templates)}"

    msg = build_message("analyst_to_strategist", t)
    assert msg["message_id"],              "message_id must be set"
    assert msg["from_brain"] == "Analyst", f"Wrong from_brain: {msg['from_brain']}"
    assert msg["to_brain"] == "Strategist",f"Wrong to_brain: {msg['to_brain']}"
    assert msg["body"],                    "Body must not be empty"
    assert "ANALYST" in msg["body"],       "Body should contain brain label"

    return f"brain_bridge OK — {len(templates)} templates, analyst→strategist message built"


def check_09_dashboard_route():
    """Dashboard route (/) returns 200 with AEGIS Command Center content."""
    return _check_route("/", "Command Center")


def check_10_tasks_route():
    """Tasks route (/tasks) returns 200."""
    return _check_route("/tasks", "Task Console")


def check_11_reports_route():
    """Reports route (/reports) returns 200."""
    return _check_route("/reports", "Reports")


def check_12_models_route():
    """Models route (/models) returns 200."""
    return _check_route("/models", "Models")


def check_13_safety_route():
    """Safety route (/safety) returns 200."""
    return _check_route("/safety", "Safety Gate")


# ---------------------------------------------------------------------------
# HTTP helpers (checks 9-13)
# ---------------------------------------------------------------------------

_server = None
_server_thread = None


def _start_test_server():
    """Start the AEGIS app on APP_PORT for HTTP checks."""
    global _server, _server_thread
    from http.server import HTTPServer
    from operator_app.db import init_db
    from operator_app.run_app import AEGISHandler

    init_db()
    _server = HTTPServer((HOST, APP_PORT), AEGISHandler)
    _server_thread = threading.Thread(target=_server.serve_forever, daemon=True)
    _server_thread.start()
    time.sleep(0.5)   # brief settle


def _stop_test_server():
    """Gracefully shut down the test server."""
    global _server
    if _server:
        _server.shutdown()
        _server = None


def _check_route(path: str, expected_text: str) -> str:
    """Hit a route on the test server and assert 200 + expected text."""
    import urllib.request as ur
    url = f"http://{HOST}:{APP_PORT}{path}"
    with ur.urlopen(url, timeout=5) as r:
        status = r.status
        body   = r.read().decode("utf-8", errors="replace")
    assert status == 200, f"{path} returned HTTP {status}"
    assert expected_text in body, \
        f"'{expected_text}' not found in {path} response (len={len(body)})"
    return f"HTTP {status} — '{expected_text}' present in response"


# ---------------------------------------------------------------------------
# Report writer
# ---------------------------------------------------------------------------

def write_report(started_at: str, finished_at: str):
    passed  = [r for r in results if r["status"] == "PASS"]
    failed  = [r for r in results if r["status"] == "FAIL"]
    total   = len(results)
    overall = "✅ ALL PASS" if not failed else f"❌ {len(failed)} FAILED"

    lines = [
        "# AEGIS Proof-of-Life Report",
        "",
        f"**Date:**    {finished_at}",
        f"**System:**  AEGIS Local AI Operator — Execution Kernel V4",
        f"**Started:** {started_at}",
        f"**Finished:**{finished_at}",
        f"**Result:**  {overall} ({len(passed)}/{total})",
        "",
        "---",
        "",
        "## Check Results",
        "",
        "| # | Check | Status | Detail |",
        "|---|-------|--------|--------|",
    ]

    for r in results:
        icon   = "✅ PASS" if r["status"] == "PASS" else "❌ FAIL"
        detail = r["detail"] or r["error"] or "—"
        detail = detail[:100].replace("|", "\\|")
        lines.append(f"| {r['number']:02d} | {r['name']} | {icon} | {detail} |")

    lines += [
        "",
        "---",
        "",
        "## Component Summary",
        "",
        "| Component | Status |",
        "|-----------|--------|",
        f"| Task Compiler         | {'✅ PASS' if any(r['number']==1 and r['status']=='PASS' for r in results) else '❌ FAIL'} |",
        f"| Safety Gate           | {'✅ PASS' if any(r['number']==2 and r['status']=='PASS' for r in results) else '❌ FAIL'} |",
        f"| Safe Action Matrix    | {'✅ PASS' if any(r['number']==3 and r['status']=='PASS' for r in results) else '❌ FAIL'} |",
        f"| Tool Router           | {'✅ PASS' if any(r['number']==4 and r['status']=='PASS' for r in results) else '❌ FAIL'} |",
        f"| Execution Kernel V4   | {'✅ PASS' if any(r['number']==5 and r['status']=='PASS' for r in results) else '❌ FAIL'} |",
        f"| Brain Council         | {'✅ PASS' if any(r['number']==6 and r['status']=='PASS' for r in results) else '❌ FAIL'} |",
        f"| Challenge Mode        | {'✅ PASS' if any(r['number']==7 and r['status']=='PASS' for r in results) else '❌ FAIL'} |",
        f"| Brain Bridge          | {'✅ PASS' if any(r['number']==8 and r['status']=='PASS' for r in results) else '❌ FAIL'} |",
        f"| Dashboard Route       | {'✅ PASS' if any(r['number']==9 and r['status']=='PASS' for r in results) else '❌ FAIL'} |",
        f"| Tasks Route           | {'✅ PASS' if any(r['number']==10 and r['status']=='PASS' for r in results) else '❌ FAIL'} |",
        f"| Reports Route         | {'✅ PASS' if any(r['number']==11 and r['status']=='PASS' for r in results) else '❌ FAIL'} |",
        f"| Models Route          | {'✅ PASS' if any(r['number']==12 and r['status']=='PASS' for r in results) else '❌ FAIL'} |",
        f"| Safety Route          | {'✅ PASS' if any(r['number']==13 and r['status']=='PASS' for r in results) else '❌ FAIL'} |",
        "",
        "---",
        "",
        "## Notes",
        "",
        "- All task executions are in **demo mode** (simulated, no real side effects)",
        "- HTTP checks run on 127.0.0.1:{APP_PORT} (dedicated proof-of-life port)".replace("{APP_PORT}", str(APP_PORT)),
        "- The operator app is available at http://127.0.0.1:7777 (run `python3 operator_app/run_app.py`)",
        "- Database: `operator_app/aegis.db` (SQLite, stdlib only)",
        "- Python: /usr/bin/python3 (stdlib only, no pip dependencies)",
        "",
        "---",
        "",
        f"**Proof-of-Life Status: {overall}**",
    ]

    with open(REPORT_PATH, "w") as f:
        f.write("\n".join(lines) + "\n")

    print(f"\n  Report saved → {REPORT_PATH}")


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

def main():
    started_at = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")

    print()
    print("=" * 65)
    print("  AEGIS Local AI Operator — Proof-of-Life Gate")
    print(f"  Started: {started_at}")
    print("=" * 65)
    print()

    # --- Checks 1-8: Pure kernel (no network) ---
    print("[Execution Kernel Checks]")
    check(1,  "Task Compiler",        check_01_task_compiler)
    check(2,  "Safety Gate",          check_02_safety_gate)
    check(3,  "Safe Action Matrix",   check_03_safe_action_matrix)
    check(4,  "Tool Router",          check_04_tool_router)
    check(5,  "Execution Kernel V4",  check_05_execution_kernel_v4)
    check(6,  "Brain Council",        check_06_brain_council)
    check(7,  "Challenge Mode",       check_07_challenge_mode)
    check(8,  "Brain Bridge",         check_08_brain_bridge)

    # --- Checks 9-13: HTTP routes ---
    print()
    print("[HTTP Route Checks — starting test server on port {APP_PORT}]".replace(
        "{APP_PORT}", str(APP_PORT)))
    try:
        _start_test_server()
        check(9,  "Dashboard Route /",      check_09_dashboard_route)
        check(10, "Tasks Route /tasks",     check_10_tasks_route)
        check(11, "Reports Route /reports", check_11_reports_route)
        check(12, "Models Route /models",   check_12_models_route)
        check(13, "Safety Route /safety",   check_13_safety_route)
    finally:
        _stop_test_server()

    # --- Summary ---
    passed  = sum(1 for r in results if r["status"] == "PASS")
    failed  = sum(1 for r in results if r["status"] == "FAIL")
    total   = len(results)
    finished_at = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")

    print()
    print("=" * 65)
    print(f"  Proof-of-Life: {passed}/{total} passed")
    if failed:
        print(f"  FAILURES ({failed}):")
        for r in results:
            if r["status"] == "FAIL":
                print(f"    ✗ [{r['number']:02d}] {r['name']}: {r['error']}")
    else:
        print("  ✅ ALL 13 CHECKS PASSED — Prompt 1 proof-of-life complete.")
    print(f"  Finished: {finished_at}")
    print("=" * 65)
    print()

    write_report(started_at, finished_at)

    return 0 if failed == 0 else 1


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