#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
PULSE Relay — Single prompt broadcasts to all agents via ASOP task board.

Usage:
  # Auto-decompose via LLM (if DISPATCHER_PROVIDER is set):
  python pulse_relay.py "Rename babel refs and build the new auth feature"

  # Manual task list (no LLM cost):
  python pulse_relay.py --tasks "Rename babel refs:documentation:low" "Build auth:features:high"

  # Just post a raw prompt (first agent that boots will decompose):
  python pulse_relay.py --raw "Fix all the security issues"

How it works:
  1. You type ONE prompt here
  2. Relay decomposes into tasks (LLM or manual)
  3. Tasks land on task_board.json
  4. Any agent that boots next sees open tasks and auto-claims them
  5. No second prompt needed — the swarm self-organizes

Dependencies: Python stdlib only (Law 4)
"""
import json
import os
import re
import secrets
import sys
import time
from datetime import datetime, timezone
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent
ROOT_DIR = SCRIPT_DIR.parent.parent

# When run directly, ensure pulse package is importable
if str(ROOT_DIR) not in sys.path:
    sys.path.insert(0, str(ROOT_DIR))
ENV_FILE = ROOT_DIR / ".env"

from pulse.core.brain_utils import (
    TASK_BOARD_FILE,
    atomic_update_json,
    now_iso as _now_iso,
    load_env,
)

# Load env on import so dispatcher gets the HMAC key
load_env(ENV_FILE)

# Import dispatcher for LLM decomposition
from pulse.tasks.pulse_dispatcher import TaskDispatcher


def _ts():
    return int(datetime.now(timezone.utc).timestamp())


def _get_tier(complexity: str) -> str:
    """Map complexity to recommended_tier.
    Supports low/medium/high and direct tier names (fast/standard/premium).
    """
    tier_map = {
        "low": "fast", "medium": "standard", "high": "premium",
        "fast": "fast", "standard": "standard", "premium": "premium"
    }
    return tier_map.get(complexity, "standard")


def parse_manual_tasks(task_strings: list[str]) -> list[dict]:
    """Parse manual task specs like 'Title:domain:complexity'.

    Format: "Task title" or "Task title:domain" or "Task title:domain:complexity"
    """
    ts = _ts()
    tasks = []
    for i, spec in enumerate(task_strings):
        parts = spec.split(":")
        title = parts[0].strip()
        domain = parts[1].strip() if len(parts) > 1 else "features"
        complexity = parts[2].strip() if len(parts) > 2 else "medium"

        tasks.append({
            "task_id": f"t_{ts}_{i + 1:03d}",
            "title": title,
            "description": title,
            "domain": domain,
            "complexity": complexity,
            "recommended_tier": _get_tier(complexity),
            "required_capabilities": [],
            "depends_on": [],
            "status": "open",
            "claimed_by": None,
            "claimed_at": None,
            "files_touched": [],
            "outcome": None,
        })
    return tasks


def post_raw_prompt(prompt: str) -> str:
    """Post a raw prompt as a single 'decompose-me' task.

    First agent that boots will see this and decompose it further.
    """
    ts = _ts()
    task = {
        "task_id": f"t_{ts}_001",
        "title": f"[DECOMPOSE] {prompt[:100]}",
        "description": (
            f"Raw prompt from user — first agent to claim should decompose "
            f"this into subtasks and update the board.\n\nOriginal prompt: {prompt}"
        ),
        "domain": "features",
        "complexity": "medium",
        "recommended_tier": _get_tier("medium"),
        "required_capabilities": [],
        "depends_on": [],
        "status": "open",
        "claimed_by": None,
        "claimed_at": None,
        "files_touched": [],
        "outcome": None,
    }
    dispatcher = TaskDispatcher(verbose=True)
    dispatch_id = dispatcher.dispatch(prompt, [task])
    return dispatch_id


def main():
    import argparse

    parser = argparse.ArgumentParser(
        description="PULSE Relay — broadcast a prompt to all agents",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  # LLM decomposition (needs DISPATCHER_PROVIDER in .env):
  python pulse_relay.py "Clean up naming and add rate limiting"

  # Manual tasks (zero LLM cost):
  python pulse_relay.py --tasks "Fix naming:refactoring:low" "Add rate limiter:security:high"

  # Raw prompt (first agent decomposes):
  python pulse_relay.py --raw "Ship the neural hardening priority"

  # Check current board:
  python pulse_relay.py --status
""",
    )
    parser.add_argument("prompt", nargs="?", help="Prompt to decompose and dispatch")
    parser.add_argument(
        "--tasks", nargs="+", metavar="SPEC",
        help="Manual task specs: 'Title:domain:complexity'",
    )
    parser.add_argument(
        "--raw", action="store_true",
        help="Post prompt as-is — first agent decomposes",
    )
    parser.add_argument(
        "--founder-key", metavar="KEY_PATH",
        help="Founder override — sign dispatch with RSA private key from authorized_humans.json",
    )
    parser.add_argument(
        "--force-unsigned", action="store_true",
        help="(Internal) Allow unsigned dispatch for system daemons",
    )
    parser.add_argument(
        "--status", action="store_true",
        help="Show current task board status",
    )

    args = parser.parse_args()

    # --- Status mode ---
    if args.status:
        _show_status()
        return

    # --- Manual tasks mode ---
    if args.tasks:
        tasks = parse_manual_tasks(args.tasks)
        prompt = args.prompt or " + ".join(t["title"] for t in tasks)
        dispatcher = TaskDispatcher(verbose=True)
        if not dispatcher._hmac_key:
            print("[RELAY] WARNING: PULSE_BRAIN_KEY not set — dispatches will be unsigned.")
            print("[RELAY] Set PULSE_BRAIN_KEY in .env for signed dispatches.")

        founder_override = args.force_unsigned
        if args.founder_key:
            from pulse.tasks.founder_auth import verify_founder_key
            founder_override = verify_founder_key(args.founder_key)
            if not founder_override:
                print("[RELAY] REJECTED: Founder key verification failed.")
                return

        dispatch_id = dispatcher.dispatch(prompt, tasks,
                                          founder_override=founder_override)
        print(f"\n[RELAY] Dispatched {len(tasks)} tasks ({dispatch_id})")
        for t in tasks:
            print(f"  [{t['status']}] {t['task_id']}: {t['title']}")
        print(f"\n[RELAY] Next agent to boot will auto-claim these.")
        return

    # --- Need a prompt for LLM or raw mode ---
    if not args.prompt:
        parser.print_help()
        return

    # --- Raw mode ---
    if args.raw:
        dispatch_id = post_raw_prompt(args.prompt)
        print(f"\n[RELAY] Posted raw prompt ({dispatch_id})")
        print(f"[RELAY] First agent to boot will decompose and execute.")
        return

    # --- LLM decomposition mode ---
    dispatcher = TaskDispatcher(verbose=True)

    if dispatcher.provider == "none":
        print("[RELAY] No DISPATCHER_PROVIDER set — falling back to raw mode.")
        print("[RELAY] Set DISPATCHER_PROVIDER=google|anthropic|openai in .env for auto-decompose.")
        dispatch_id = post_raw_prompt(args.prompt)
        print(f"\n[RELAY] Posted raw prompt ({dispatch_id})")
        return

    print(f"[RELAY] Decomposing via {dispatcher.provider}...")
    tasks = dispatcher.decompose_with_llm(args.prompt)

    if not tasks:
        print("[RELAY] LLM returned no tasks — falling back to raw mode.")
        dispatch_id = post_raw_prompt(args.prompt)
        print(f"\n[RELAY] Posted raw prompt ({dispatch_id})")
        return

    # Law 9: Ensure all tasks have recommended_tier
    for t in tasks:
        if "recommended_tier" not in t:
            t["recommended_tier"] = _get_tier(t.get("complexity", "medium"))

    dispatch_id = dispatcher.dispatch(args.prompt, tasks)
    print(f"\n[RELAY] Dispatched {len(tasks)} tasks ({dispatch_id})")
    for t in tasks:
        deps = t.get("depends_on", [])
        dep_str = f" (blocked by: {deps})" if deps else ""
        print(f"  [{t['status']}] {t['task_id']}: {t['title']}{dep_str}")
    print(f"\n[RELAY] Next agent to boot will auto-claim open tasks.")


def _show_status():
    """Print current task board summary."""
    from pulse.core.brain_utils import load_json_dict

    board = load_json_dict(TASK_BOARD_FILE)
    dispatches = board.get("dispatches", [])

    if not dispatches:
        print("[RELAY] Task board is empty. Use pulse_relay.py to dispatch work.")
        return

    total = 0
    by_status = {}
    for dispatch in dispatches:
        print(f"\nDispatch: {dispatch.get('dispatch_id', '?')}")
        print(f"  Prompt: {dispatch.get('prompt', '?')[:120]}")
        print(f"  Posted: {dispatch.get('dispatched_at', '?')}")
        for t in dispatch.get("tasks", []):
            total += 1
            status = t.get("status", "?")
            by_status[status] = by_status.get(status, 0) + 1
            claimed = t.get("claimed_by", "")
            claim_info = f" -> {claimed}" if claimed else ""
            print(f"    [{status}]{claim_info} {t['task_id']}: {t.get('title', '?')}")

    print(f"\nTotal: {total} tasks | " + " | ".join(f"{s}: {c}" for s, c in sorted(by_status.items())))


if __name__ == "__main__":
    main()
