#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Core background loop for swarm autopilot."""
from __future__ import annotations

import os
import subprocess
import sys
import time

from pulse.cli import ROOT_DIR
from pulse.tasks.task_ops import (
    verify_constitution, claim_task, finalize_task, TASK_BOARD, TOOLS_DIR,
)
from pulse.cli.swarm_workers import _build_agent_cmd

from pulse.cli.autopilot.context import _build_work_order, _write_context_file
from pulse.cli.autopilot.gates import _diff_snapshot, _run_gates


def _run_autopilot_loop(agent: str, model: str, tier: str,
                        worker_id: str | None = None, skills_used: bool = True):
    """Deterministic autopilot: claim -> launch agent CLI -> mark done -> repeat.

    Designed to run as a background process (stdout goes to log file).
    """
    import logging
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s [%(name)s] %(levelname)s %(message)s",
        datefmt="%H:%M:%S",
    )

    if not verify_constitution():
        logging.error("CONSTITUTION HASH MISMATCH -- refusing to start.")
        return 1

    agent_id = worker_id or (f"{agent}_{model}" if model else f"{agent}_cli")
    log = logging.getLogger(agent_id)
    cycle = 0
    task_timeout = 300
    max_diff_lines = 500
    consecutive_fails: dict[str, int] = {}
    MAX_RETRIES = 3

    log.info("Autopilot started (tier=%s, model=%s, skills_used=%s)", tier, model, skills_used)

    consecutive_errors = 0
    MAX_CONSECUTIVE_ERRORS = 10

    try:
        while True:
            try:
                task = claim_task(TASK_BOARD, agent_id, tier=tier)
            except Exception as e:
                consecutive_errors += 1
                log.error("Poll error (%d/%d): %s", consecutive_errors,
                          MAX_CONSECUTIVE_ERRORS, e)
                if consecutive_errors >= MAX_CONSECUTIVE_ERRORS:
                    log.error("Too many consecutive errors -- shutting down.")
                    return 1
                time.sleep(10)
                continue
            consecutive_errors = 0

            if not task:
                time.sleep(5)
                continue

            cycle += 1
            task_id = task.get("task_id") or task.get("id", "unknown")
            title = task.get("title", "Unknown")
            description = task.get("description", title)
            domain = task.get("domain", "general")

            # V4 fast-path: memory tasks bypass CLI sessions entirely
            from pulse.workers.runner.worker_memory import is_memory_task, execute_memory_task
            if is_memory_task(task):
                log.info("MEMORY FAST-PATH: [%s] %s", task_id, title[:60])
                try:
                    execute_memory_task(task, agent_id, log)
                except Exception as e:
                    log.error("Memory task error: %s", e, exc_info=True)
                    finalize_task(TASK_BOARD, task_id, agent_id, status="open")
                log.info("Cycle %d complete. Checking for more...", cycle)
                time.sleep(1)
                continue

            if consecutive_fails.get(task_id, 0) >= MAX_RETRIES:
                log.warning("Skipping [%s] -- failed %d times, leaving for another worker",
                            task_id, MAX_RETRIES)
                finalize_task(TASK_BOARD, task_id, agent_id, status="open")
                time.sleep(5)
                continue

            log.info("Cycle %d -- [%s]: %s", cycle, task_id, title[:60])

            # Brain query
            brain_context = ""
            try:
                result = subprocess.run(
                    [sys.executable, str(TOOLS_DIR / "brain_query.py"),
                     "--query", f"{title} {domain}"],
                    capture_output=True, text=True, timeout=15, cwd=str(ROOT_DIR),
                    encoding="utf-8", errors="replace",
                )
                if result.stdout.strip():
                    brain_context = result.stdout.strip()
            except Exception:
                pass

            # Build work order + context file
            work_order, loaded_skills = _build_work_order(
                task_id, title, description, domain, brain_context,
                skills_used=skills_used, agent_id=agent_id)
            context_file, boot_message = _write_context_file(
                agent, agent_id, tier, work_order, f"{title} {domain}")
            cmd = _build_agent_cmd(agent, model, boot_message)
            if not cmd:
                log.error("Agent CLI not found: %s", agent)
                return 1

            pre_snapshot = _diff_snapshot()

            log.info("Launching %s...", agent)
            start_time = time.time()
            agent_crashed = False
            try:
                env = os.environ.copy()
                env.pop("CLAUDECODE", None)
                result = subprocess.run(
                    cmd, cwd=str(ROOT_DIR), timeout=task_timeout,
                    env=env, stdin=subprocess.DEVNULL)
                elapsed = int(time.time() - start_time)
                log.info("Agent finished in %ds (exit=%d)", elapsed, result.returncode)
                if elapsed < 5 or result.returncode != 0:
                    agent_crashed = True
                    log.warning("Agent likely failed (too fast or non-zero exit)")
            except subprocess.TimeoutExpired:
                elapsed = int(time.time() - start_time)
                log.warning("Timeout (%ds). Force-cycling...", task_timeout)

            try:
                context_file.unlink(missing_ok=True)
            except Exception:
                pass

            # Post-task validation gates
            if agent_crashed:
                gates_passed = False
                gate_failures = [f"agent-crash: {elapsed}s"]
                post_snapshot = _diff_snapshot()
            else:
                gates_passed, gate_failures, post_snapshot = _run_gates(
                    max_diff_lines, pre_snapshot=pre_snapshot)

            if gates_passed:
                finalize_task(TASK_BOARD, task_id, agent_id, status="done")
                consecutive_fails.pop(task_id, None)
                log.info("Task [%s] DONE", task_id)
            else:
                consecutive_fails[task_id] = consecutive_fails.get(task_id, 0) + 1
                finalize_task(TASK_BOARD, task_id, agent_id, status="open")
                log.warning("Task [%s] FAILED (%d/%d) -- %s (reopened)",
                            task_id, consecutive_fails[task_id], MAX_RETRIES,
                            ", ".join(gate_failures))

                # D5: Record gate failure -> temporary anti-pattern
                try:
                    from pulse.daemons.neural.gate_feedback import record_gate_failure
                    corrective = record_gate_failure(
                        task_id, title, description, domain,
                        gate_failures, post_snapshot)
                    if corrective:
                        log.info("[D5] Gate failure captured — anti-pattern written")
                except Exception as e:
                    log.debug("[D5] gate_feedback error: %s", e)

            # Save learnings
            try:
                save_cmd = [
                    sys.executable, str(TOOLS_DIR / "pulse_save.py"),
                    "--agent", agent_id, "--learnings", f"Completed: {title}",
                ]
                if loaded_skills:
                    save_cmd.extend(["--skills-used", ",".join(loaded_skills)])
                subprocess.run(save_cmd, timeout=10, capture_output=True,
                               encoding="utf-8", errors="replace")
            except Exception:
                pass

            log.info("Cycle %d complete. Checking for more...", cycle)
            if agent_crashed and elapsed < 10:
                log.info("Fast crash -- backing off 30s")
                time.sleep(30)
            else:
                time.sleep(2)

    except KeyboardInterrupt:
        log.info("Stopped after %d cycle(s).", cycle)
        return 0
