"""
agent_floor_keeper.py — Genesis Constitution Article 3.3 Enforcer

Maintains a minimum of 200 active agents at ALL times.
Provisions agents when the floor is breached, logs every check,
and writes provisioning requests into loop/tasks.json.

CLI:
    python3 agent_floor_keeper.py --check    # report status only
    python3 agent_floor_keeper.py --enforce  # check + provision if needed
"""

import argparse
import json
import os
import re
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path

# ---------------------------------------------------------------------------
# Paths — ALL on E: drive (C: drive FORBIDDEN per Genesis Constitution)
# ---------------------------------------------------------------------------
GENESIS_ROOT   = Path("/mnt/e/genesis-system")
AGENTS_DIR     = GENESIS_ROOT / "loop" / "agents"
MANIFEST_PATH  = GENESIS_ROOT / ".claude" / "agents" / "MANIFEST.md"
TASKS_JSON     = GENESIS_ROOT / "loop" / "tasks.json"
LOGS_DIR       = GENESIS_ROOT / "logs"
LOG_FILE       = LOGS_DIR / "agent_floor_keeper.log"

AGENT_FLOOR    = 200
WARNING_FLOOR  = 200    # < 200  → WARNING
CRITICAL_FLOOR = 50     # < 50   → CRITICAL


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _utc_now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _ensure_logs_dir() -> None:
    LOGS_DIR.mkdir(parents=True, exist_ok=True)


def _log(message: str) -> None:
    """Append a timestamped line to the floor-keeper log."""
    _ensure_logs_dir()
    ts = _utc_now()
    line = f"[{ts}] {message}\n"
    with open(LOG_FILE, "a", encoding="utf-8") as fh:
        fh.write(line)


# ---------------------------------------------------------------------------
# 1. get_active_agent_count
# ---------------------------------------------------------------------------

def _count_from_agents_dir() -> int:
    """Count agent descriptor files under loop/agents/."""
    if not AGENTS_DIR.exists():
        return 0
    agent_extensions = {".json", ".yaml", ".yml", ".md", ".txt"}
    count = sum(
        1 for p in AGENTS_DIR.iterdir()
        if p.is_file() and p.suffix.lower() in agent_extensions
    )
    return count


def _count_from_manifest() -> int:
    """
    Parse MANIFEST.md for agent entries.

    Counts rows in markdown tables that contain a known model tier keyword
    (opus, sonnet, haiku) — these represent defined agent entries.
    """
    if not MANIFEST_PATH.exists():
        return 0

    content = MANIFEST_PATH.read_text(encoding="utf-8")

    # Match table rows: lines that start with | and contain a model name
    model_pattern = re.compile(
        r"^\|\s*[\w\-]+\s*\|\s*(opus|sonnet|haiku)",
        re.IGNORECASE | re.MULTILINE,
    )
    matches = model_pattern.findall(content)
    return len(matches)


def _count_from_processes() -> int:
    """
    Count running Python processes that look like Genesis agents.

    Looks for: genesis, agent_floor, rlm_army, gemini_rate_maximizer,
    persistent_worker, multi_model_swarm, genesis_execution_layer,
    continuous_evolution keywords in the command line.
    """
    genesis_keywords = [
        "genesis",
        "agent_floor",
        "rlm_army",
        "gemini_rate_maximizer",
        "persistent_worker",
        "multi_model_swarm",
        "genesis_execution_layer",
        "continuous_evolution",
        "genesis_heartbeat",
        "genesis_kernel",
        "rlm_bloodstream",
    ]
    try:
        result = subprocess.run(
            ["ps", "aux"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        lines = result.stdout.splitlines()
        count = 0
        for line in lines:
            lower = line.lower()
            if "python" in lower and any(kw in lower for kw in genesis_keywords):
                count += 1
        return count
    except Exception:
        return 0


def get_active_agent_count() -> int:
    """
    Best-estimate count of active agents across all three sources:
      a) loop/agents/ directory file count
      b) MANIFEST.md parsed agent rows
      c) Running genesis-related Python processes
    """
    dir_count      = _count_from_agents_dir()
    manifest_count = _count_from_manifest()
    process_count  = _count_from_processes()

    # The manifest is authoritative for defined agents; processes add liveness.
    # We take the maximum of (manifest + processes) and dir_count to avoid
    # double-counting manifest entries that are also running.
    best_estimate = max(manifest_count + process_count, dir_count)
    return best_estimate


# ---------------------------------------------------------------------------
# 2. get_agent_floor_status
# ---------------------------------------------------------------------------

def get_agent_floor_status() -> dict:
    """
    Returns a status dict describing current agent count vs the 200-agent floor.

    {
        "current_count": int,
        "floor": 200,
        "gap": int,          # 0 if at or above floor
        "status": "OK" | "WARNING" | "CRITICAL",
        "pct_of_floor": float   # e.g. 0.75 means 75% of floor met
    }
    """
    current = get_active_agent_count()
    gap     = max(0, AGENT_FLOOR - current)

    if current >= AGENT_FLOOR:
        status = "OK"
    elif current < CRITICAL_FLOOR:
        status = "CRITICAL"
    else:
        status = "WARNING"

    pct_of_floor = round(current / AGENT_FLOOR, 4)

    return {
        "current_count": current,
        "floor": AGENT_FLOOR,
        "gap": gap,
        "status": status,
        "pct_of_floor": pct_of_floor,
    }


# ---------------------------------------------------------------------------
# 3. provision_agents_to_floor
# ---------------------------------------------------------------------------

def provision_agents_to_floor(target: int = 200) -> dict:
    """
    If current agent count < target, write a provisioning task to
    loop/tasks.json and return a result dict.

    Task format appended to the "stories" array (matching existing schema):
    {
        "id": "floor-keeper-YYYYMMDD",
        "type": "provision_agents",
        "priority": "HIGH",
        "target_count": N,
        "current_count": N,
        "gap": N,
        "created": "<ISO timestamp>",
        "status": "pending",
        "revenue_impact": "critical - floor mandate"
    }
    """
    status_info = get_agent_floor_status()
    current     = status_info["current_count"]
    gap         = max(0, target - current)

    if gap == 0:
        return {
            "provisioned": False,
            "gap": 0,
            "task_id": None,
            "reason": f"Floor met — {current}/{target} agents active.",
        }

    today    = datetime.now(timezone.utc).strftime("%Y%m%d")
    task_id  = f"floor-keeper-{today}"
    now_iso  = _utc_now()

    new_task = {
        "id":             task_id,
        "type":           "provision_agents",
        "priority":       "HIGH",
        "target_count":   target,
        "current_count":  current,
        "gap":            gap,
        "created":        now_iso,
        "status":         "pending",
        "revenue_impact": "critical - floor mandate",
    }

    # Load or initialise tasks.json
    if TASKS_JSON.exists():
        try:
            data = json.loads(TASKS_JSON.read_text(encoding="utf-8"))
        except json.JSONDecodeError:
            data = {}
    else:
        data = {}

    # Ensure a stories/tasks array exists (support both key names)
    if "stories" in data:
        array_key = "stories"
    elif "tasks" in data:
        array_key = "tasks"
    else:
        array_key = "stories"
        data[array_key] = []

    # Deduplicate: remove any prior floor-keeper task with same date id
    data[array_key] = [
        t for t in data[array_key]
        if not (isinstance(t, dict) and t.get("id") == task_id)
    ]
    data[array_key].append(new_task)
    data["updated_at"] = now_iso

    TASKS_JSON.write_text(
        json.dumps(data, indent=2, ensure_ascii=False),
        encoding="utf-8",
    )

    return {
        "provisioned": True,
        "gap":         gap,
        "task_id":     task_id,
    }


# ---------------------------------------------------------------------------
# 4. check_and_enforce
# ---------------------------------------------------------------------------

def check_and_enforce() -> dict:
    """
    Main entry point.
      1. Get current floor status.
      2. Log the result.
      3. If below floor, call provision_agents_to_floor().
      4. Return a full status report dict.
    """
    status      = get_agent_floor_status()
    now_iso     = _utc_now()
    report: dict = {
        "checked_at":    now_iso,
        "floor_status":  status,
        "action_taken":  None,
        "provision_result": None,
    }

    log_line = (
        f"FLOOR CHECK | status={status['status']} "
        f"current={status['current_count']} "
        f"floor={status['floor']} "
        f"gap={status['gap']} "
        f"pct={status['pct_of_floor']:.1%}"
    )
    _log(log_line)

    if status["gap"] > 0:
        _log(f"BELOW FLOOR — provisioning {status['gap']} agents (target={AGENT_FLOOR})")
        prov = provision_agents_to_floor(target=AGENT_FLOOR)
        report["action_taken"]     = "provision_agents"
        report["provision_result"] = prov
        _log(f"PROVISION RESULT | {prov}")
    else:
        report["action_taken"] = "none_required"
        _log("Floor OK — no provisioning needed.")

    return report


# ---------------------------------------------------------------------------
# 5. CLI
# ---------------------------------------------------------------------------

def _print_status_report(report: dict) -> None:
    s  = report["floor_status"]
    pr = report.get("provision_result")

    print("=" * 56)
    print("  GENESIS AGENT FLOOR KEEPER — Article 3.3 Report")
    print("=" * 56)
    print(f"  Checked at    : {report['checked_at']}")
    print(f"  Current agents: {s['current_count']}")
    print(f"  Floor target  : {s['floor']}")
    print(f"  Gap           : {s['gap']}")
    print(f"  % of floor    : {s['pct_of_floor']:.1%}")
    print(f"  Status        : {s['status']}")
    print(f"  Action taken  : {report['action_taken']}")
    if pr:
        if pr.get("provisioned"):
            print(f"  Task ID       : {pr['task_id']}")
            print(f"  Provisioning  : {pr['gap']} agents queued in loop/tasks.json")
        else:
            print(f"  Provisioning  : {pr.get('reason', 'skipped')}")
    print("=" * 56)


def _print_check_only(status: dict) -> None:
    print("=" * 56)
    print("  GENESIS AGENT FLOOR KEEPER — Status Check")
    print("=" * 56)
    print(f"  Current agents: {status['current_count']}")
    print(f"  Floor target  : {status['floor']}")
    print(f"  Gap           : {status['gap']}")
    print(f"  % of floor    : {status['pct_of_floor']:.1%}")
    print(f"  Status        : {status['status']}")
    print("=" * 56)


def main() -> None:
    parser = argparse.ArgumentParser(
        description="Genesis Agent Floor Keeper — Article 3.3 enforcement."
    )
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument(
        "--check",
        action="store_true",
        help="Report current agent floor status (no provisioning).",
    )
    group.add_argument(
        "--enforce",
        action="store_true",
        help="Check status and provision agents if below floor.",
    )
    args = parser.parse_args()

    if args.check:
        status = get_agent_floor_status()
        _print_check_only(status)
        sys.exit(0 if status["status"] == "OK" else 1)

    if args.enforce:
        report = check_and_enforce()
        _print_status_report(report)
        s = report["floor_status"]["status"]
        sys.exit(0 if s == "OK" else 1)


if __name__ == "__main__":
    main()
