#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""
PULSE Boot Script: Standardized agent initialization.

Every agent runs this on boot. Produces:
1. Registers agent in the swarm
2. Loads full Constitution + Laws (governance is non-negotiable)
3. Checks task board, optionally auto-claims a matching task
4. Searches brain for relevant anti-patterns and knowledge
5. Outputs a standardized status card (same format, every agent)

Usage:
    python pulse_boot.py --agent claude --tier premium [--claim]
    python pulse_boot.py --agent gemini --tier standard
    python pulse_boot.py --reprocess-logs  (retroactive knowledge extraction)

Task board logic delegated to boot_tasks.py (split for Law 7).
Content building delegated to boot_briefing.py (split for Law 7).
Dependencies: Python stdlib + brain_utils, boot_tasks, boot_briefing (internal)
"""
import argparse
import io
import json
import os
import sys
from pathlib import Path

# Windows Unicode fix — only when running as main script
if os.name == 'nt' and __name__ == '__main__':
    if hasattr(sys.stdout, 'reconfigure'):
        sys.stdout.reconfigure(encoding='utf-8', errors='replace')
    if hasattr(sys.stderr, 'reconfigure'):
        sys.stderr.reconfigure(encoding='utf-8', errors='replace')

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))

from pulse.core.brain_utils import (
    load_json_dict, save_json,
    AGENT_REGISTRY_FILE, now_iso as _now_iso,
)
from pulse.brain.boot_briefing import (
    load_recent_brain, load_relevant_brain, search_brain_for_task,
    load_daemon_injections, build_brain_briefing, load_curriculum,
)
from pulse.brain.boot.formatters_ext import build_status_card, build_governance_context
# Re-export task board functions for backward compat
from pulse.brain.boot.tasks import (  # noqa: F401
    get_task_board, find_matching_task, claim_task, _AGENT_MAP,
)
# Re-export agent helpers from boot_agent (extracted for Law 7 SoC)
from pulse.brain.boot.agent import (  # noqa: F401
    auto_detect_tier, register_agent, get_boot_briefing_cached,
    _BOOT_CACHE, _BOOT_CACHE_TTL,
)

def boot(agent_name: str, tier: str, do_claim: bool = False,
         task_query: str = "") -> str:
    """Full boot sequence. Returns context string for agent injection.

    Item 100: Internally checks the boot briefing cache before regenerating.
    Cache is keyed on (tier, task_query) and expires after _BOOT_CACHE_TTL seconds.
    Cache is bypassed when do_claim=True (task claiming must always run live).
    """
    import time

    if not tier or tier.lower() == 'auto':
        tier = auto_detect_tier(agent_name)

    # Item 100: Return cached briefing if fresh and no live claim needed
    if not do_claim:
        try:
            cache_key = f"{tier}:{task_query}"
            cached = _BOOT_CACHE.get("briefing")
            cached_key = _BOOT_CACHE.get("cache_key", "")
            cached_ts = _BOOT_CACHE.get("timestamp", 0.0)
            if cached and cached_key == cache_key and (time.time() - cached_ts) < _BOOT_CACHE_TTL:
                return cached
        except Exception:
            pass

    output_parts = []

    # 1. Register
    register_agent(agent_name, tier)

    # 1b. P47: Start a session for cross-session persistence
    # Run in thread with 2s timeout — DB lock can block for 60s+ (P47 busy_timeout)
    _active_session_id = None
    try:
        import threading
        from pulse.brain.session.session_lifecycle import start_session
        _sid_holder = [None]
        def _start():
            try:
                _sid_holder[0] = start_session(agent_name, domain='general')
            except Exception:
                pass
        t = threading.Thread(target=_start, daemon=True)
        t.start()
        t.join(timeout=2)
        _active_session_id = _sid_holder[0]
    except Exception:
        pass

    # 2. Check task board early (need domain for credit-driven boot)
    tasks = get_task_board()
    matched_task = find_matching_task(tasks, tier) if tasks else {}
    task_domain = matched_task.get("domain", "") if matched_task else ""

    # 3. IMPERATIVE: Load brain knowledge (Phase 4: pass task_type for credit adaptation)
    if task_query:
        brain_data = load_relevant_brain(task_query, task_type=task_domain)
    else:
        brain_data = load_recent_brain(agent_name=agent_name)

    # 3b. Cognitive Load Gating — tier-dependent briefing volume
    _TIER_LIMITS = {
        "fast":     {"lessons": 3, "failures": 3, "anti_patterns": 5, "patterns": 2, "decisions": 2},
        "standard": {"lessons": 7, "failures": 5, "anti_patterns": 8, "patterns": 4, "decisions": 3},
        "premium":  {"lessons": 15, "failures": 10, "anti_patterns": 15, "patterns": 8, "decisions": 5},
    }
    limits = _TIER_LIMITS.get(tier, _TIER_LIMITS["standard"])
    for key, limit in limits.items():
        if key in brain_data and isinstance(brain_data[key], list):
            brain_data[key] = brain_data[key][:limit]

    # 4. Auto-claim if requested (matched_task found in step 2)
    if do_claim and matched_task:
        if not claim_task(matched_task, agent_name):
            matched_task = {}

    # 5. Task-specific brain search
    task_title = matched_task.get("title", "") if matched_task else ""
    task_search = search_brain_for_task(task_title)

    # 5b. D4+D5+D6+U7: Inject daemon state + cross-agent intel
    injections = load_daemon_injections(agent_name=agent_name)
    brain_data["synthetic_constraints"] = injections["synthetic_constraints"]
    brain_data["gate_feedback"] = injections["gate_feedback"]
    brain_data["golden_paths"] = injections["golden_paths"]
    brain_data["cross_agent_intel"] = injections["cross_agent_intel"]

    # 5c. D15: Inject domain curriculum (standard + premium only — too heavy for fast tier)
    if tier != "fast":
        task_domain = matched_task.get("domain", "") if matched_task else ""
        curriculum = load_curriculum(domain=task_domain, task_query=task_title or task_query)
        if curriculum:
            brain_data["curriculum"] = curriculum

    # 5d. Phase 5-6: Inject swarm competence, autonomy, culture, decisions, intentions
    try:
        from pulse.brain.boot.loaders import load_swarm_competence
        competence = load_swarm_competence()
        if competence:
            brain_data["swarm_competence"] = competence
    except Exception:
        pass
    try:
        from pulse.core.autonomy_state import get_trust_level, LEVEL_NAMES
        level = get_trust_level()
        brain_data["autonomy_level"] = f"Level {level} ({LEVEL_NAMES.get(level, 'Unknown')})"
    except Exception:
        pass
    try:
        from pulse.daemons.consolidation.culture_synth import load_principles
        principles = load_principles()
        if principles:
            brain_data["culture_principles"] = principles
    except Exception:
        pass
    try:
        from pulse.comms.collective_decision import get_open_decisions
        decisions = get_open_decisions()
        if decisions:
            brain_data["pending_decisions"] = decisions
    except Exception:
        pass
    try:
        from pulse.daemons.synthesis.planning.prospective_memory import get_pending_intentions
        intentions = get_pending_intentions()
        if intentions:
            brain_data["pending_intentions"] = intentions
    except Exception:
        pass

    # 6. Status card (compact — user sees this)
    output_parts.append(build_status_card(agent_name, tier, matched_task, task_search))

    # 7. MANDATORY BRAIN BRIEFING (agent sees this — cannot skip)
    output_parts.append(build_brain_briefing(brain_data, task_search))

    # 7b. P47: Cross-session memory — inject recent sessions from ALL agents
    try:
        from pulse.brain.session.session_context import build_recent_sessions_context
        sessions_ctx = build_recent_sessions_context(limit=3)
        if sessions_ctx:
            output_parts.append(sessions_ctx)
    except Exception:
        pass

    # 7c. P2-4: Prior session checkpoint (agent-specific)
    try:
        from pulse.brain.boot.loaders import load_session_continuity
        continuity = load_session_continuity(agent_name)
        if continuity:
            output_parts.append(continuity)
    except Exception:
        pass

    # 8. Governance context
    output_parts.append(build_governance_context())

    # 9. Open tasks summary
    if tasks and not matched_task:
        output_parts.append("## OPEN TASKS (unclaimed)")
        for t in tasks[:10]:
            rec = t.get("recommended_tier", "?")
            output_parts.append(f"  [{rec}] {t.get('title', '?')} ({t.get('id', '?')})")
    # 10. Instructions
    output_parts.append("## AGENT INSTRUCTIONS")
    if matched_task:
        output_parts.append(f"You have claimed task: {matched_task.get('title', '?')}")
        output_parts.append(f"Task ID: {matched_task.get('id', '?')}")
        output_parts.append(f"Domain: {matched_task.get('domain', '?')}")
        output_parts.append("1. Apply the BRAIN BRIEFING above to this task.")
        output_parts.append("2. Before EVERY action, run: python pulse/brain/brain_query.py --query \"<what you are doing>\"")
        output_parts.append("3. Execute following Constitution and Laws.")
        output_parts.append("4. Output a brief Status Report (name, task, 1-2 brain insights). NO ceremony.")
        output_parts.append("5. Do the work.")
        output_parts.append("6. When done: python pulse/brain/pulse_save.py")
        output_parts.append("7. Decompose subtasks via relay — background workers auto-execute them.")
    else:
        output_parts.append("No matching tasks available. Output a brief Status Report and WAIT.")
        output_parts.append("Do NOT start autonomous work without a task assignment.")
        output_parts.append("Background workers are polling — relay tasks to the board and they'll be claimed automatically.")
    result = "\n".join(output_parts)

    # Item 100: Cache the briefing for future calls within TTL window
    try:
        _BOOT_CACHE["briefing"] = result
        _BOOT_CACHE["timestamp"] = time.time()
        _BOOT_CACHE["cache_key"] = f"{tier}:{task_query}"
    except Exception:
        pass

    return result


def main():
    parser = argparse.ArgumentParser(description="PULSE Standardized Agent Boot")
    parser.add_argument("--agent", default="unknown", help="Agent name")
    parser.add_argument("--tier", default="standard", choices=["fast", "standard", "premium"])
    parser.add_argument("--claim", action="store_true", help="Auto-claim a matching task")
    parser.add_argument("--reprocess-logs", action="store_true",
                        help="Retroactive: extract knowledge from all existing logs")
    parser.add_argument("--json", action="store_true", help="Output as JSON")
    args = parser.parse_args()

    if args.reprocess_logs:
        from pulse.brain.reprocess_logs import reprocess_logs
        reprocess_logs()
        return

    result = boot(args.agent, args.tier, args.claim)
    if args.json:
        print(json.dumps({"boot_context": result, "agent": args.agent, "tier": args.tier}))
    else:
        print(result)


if __name__ == "__main__":
    main()
