"""Hive CEO -- Primary orchestrator agent.

Routes tasks to sub-agents, manages the approval queue,
coordinates inter-agent communication, escalates to admin,
and drives the 8-step self-improvement cycle.

This is the BRAIN that orchestrates self-modification:
  Observe → Research → Propose → Approve → Build → Deploy → Verify → Learn
"""

from __future__ import annotations

import logging
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from pathlib import Path

from hive.agents.base import AgentMessage, AgentRole, BaseAgent, MessageType, Priority

logger = logging.getLogger("hive.ceo")


class HiveCEO(BaseAgent):
    """Primary orchestrator -- the hub in the hub-and-spoke model.

    Drives the 8-step improvement cycle by coordinating sub-agents:
    1. OBSERVE: Analyst indexes codebase, Watcher collects health metrics
    2. RESEARCH: Advisor researches solutions using LLM + memory
    3. PROPOSE: Advisor creates proposals, CEO adds to approval queue
    4. APPROVE: Admin reviews and approves/rejects via admin panel
    5. BUILD: Builder creates branch, generates code, runs sandbox
    6. DEPLOY: Builder creates PR, admin merges
    7. VERIFY: Watcher monitors post-deploy health
    8. LEARN: Analyst records outcomes in institutional memory
    """

    role = AgentRole.CEO

    def __init__(self, workspace_root: Path) -> None:
        super().__init__(workspace_root)
        self._sub_agents: dict[AgentRole, Any] = {}
        self._approval_queue: list[dict[str, Any]] = []
        self._active_incidents: list[dict[str, Any]] = []
        self._cycle_count: int = 0
        self._max_concurrent_cycles: int = 3
        self._active_cycles: list[dict[str, Any]] = []
        self._emergency_mode: bool = False
        self._signal_queue: list[dict[str, Any]] = []
        self._load_approval_queue()

    def _load_approval_queue(self) -> None:
        """Load persisted approval queue from .hive/backlog/."""
        backlog_dir = self._hive_dir / "backlog" / "items"
        if not backlog_dir.exists():
            return
        for item_path in sorted(backlog_dir.glob("HIVE-*.md")):
            try:
                content = item_path.read_text(encoding="utf-8")
                # Parse the structured markdown
                item: dict[str, Any] = {"file": str(item_path)}
                for line in content.splitlines():
                    if line.startswith("- id:"):
                        item["id"] = line.split(":", 1)[1].strip()
                    elif line.startswith("- status:"):
                        item["status"] = line.split(":", 1)[1].strip()
                    elif line.startswith("- title:"):
                        item["title"] = line.split(":", 1)[1].strip()
                    elif line.startswith("- priority:"):
                        item["priority"] = line.split(":", 1)[1].strip()
                if item.get("id") and item.get("status") == "proposed":
                    self._approval_queue.append(item)
            except Exception:
                continue

    def register_agent(self, agent: BaseAgent) -> None:
        """Register a sub-agent with the CEO."""
        self._sub_agents[agent.role] = agent
        logger.info("Registered sub-agent: %s", agent.name)

    # ------------------------------------------------------------------
    # The 8-Step Improvement Cycle
    # ------------------------------------------------------------------

    async def run_improvement_cycle(self) -> dict[str, Any]:
        """Execute one full improvement cycle (Steps 1-3: Observe→Research→Propose).

        Steps 4-8 happen asynchronously (admin approval, build, deploy, verify, learn).
        This method runs the automated portion of the cycle.
        """
        if self._emergency_mode:
            logger.warning("Emergency mode active -- improvement cycles paused")
            return {"status": "paused", "reason": "emergency_mode"}

        if len(self._active_cycles) >= self._max_concurrent_cycles:
            logger.info("Max concurrent cycles reached (%d)", self._max_concurrent_cycles)
            return {"status": "at_capacity", "active_cycles": len(self._active_cycles)}

        self._cycle_count += 1
        cycle_id = f"CYCLE-{self._cycle_count:03d}"
        cycle: dict[str, Any] = {
            "id": cycle_id,
            "started": datetime.now(timezone.utc).isoformat(),
            "status": "running",
            "steps_completed": [],
        }
        self._active_cycles.append(cycle)
        logger.info("Starting improvement cycle: %s", cycle_id)

        # Step 1: OBSERVE
        analyst = self._sub_agents.get(AgentRole.ANALYST)
        watcher = self._sub_agents.get(AgentRole.WATCHER)

        codebase_map = {}
        health_result = {}

        if analyst:
            codebase_map = analyst.index_codebase()
            cycle["steps_completed"].append("observe:codebase_indexed")

        if watcher:
            health_result = await watcher.check_health()
            cycle["steps_completed"].append("observe:health_checked")

        # Step 2-3: RESEARCH + PROPOSE
        advisor = self._sub_agents.get(AgentRole.ADVISOR)
        proposals = []

        if advisor and codebase_map:
            # Static analysis observations
            observations = advisor.analyze_codebase(codebase_map)
            cycle["steps_completed"].append(f"observe:found_{len(observations)}_observations")

            if observations:
                # LLM-powered research and proposal generation
                proposals = await advisor.analyze_and_propose(codebase_map, observations)
                cycle["steps_completed"].append(f"propose:{len(proposals)}_proposals")

                # Add proposals to approval queue
                for proposal in proposals:
                    self.add_to_approval_queue(proposal)

        cycle["status"] = "proposals_ready"
        cycle["proposal_count"] = len(proposals)
        cycle["health_score"] = health_result.get("score")

        logger.info(
            "Cycle %s complete: %d observations, %d proposals, health=%.1f",
            cycle_id,
            len(proposals),
            len(proposals),
            health_result.get("score", 0),
        )

        return cycle

    async def execute_approved_items(self) -> list[dict[str, Any]]:
        """Execute all approved items in the queue (Steps 5-6: Build→Deploy).

        Called after admin approves items via the admin panel.
        """
        results: list[dict[str, Any]] = []
        builder = self._sub_agents.get(AgentRole.BUILDER)

        if not builder:
            logger.error("Builder agent not registered")
            return results

        approved = [i for i in self._approval_queue if i.get("status") == "approved"]

        for item in approved:
            if self._emergency_mode:
                break

            logger.info("Executing approved item: %s", item.get("id"))

            # Import here to avoid circular dependency
            from hive.agents.builder import BuildTask

            task = BuildTask(
                item_id=item.get("id", "HIVE-000"),
                title=item.get("title", "untitled"),
                problem=item.get("problem", ""),
                solution=item.get("solution", ""),
                files_to_modify=item.get("files_to_modify", []),
                files_to_create=item.get("files_to_create", []),
                risk=item.get("risk", "low"),
            )

            completed_task = await builder.execute_task(task)
            item["status"] = completed_task.status
            item["pr_url"] = completed_task.pr_url

            results.append(
                {
                    "item_id": item.get("id"),
                    "status": completed_task.status,
                    "pr_url": completed_task.pr_url,
                    "iterations": completed_task.iterations,
                }
            )

        return results

    async def verify_deployments(self) -> list[dict[str, Any]]:
        """Verify recently deployed changes (Step 7: Verify)."""
        watcher = self._sub_agents.get(AgentRole.WATCHER)
        if not watcher:
            return []

        results = []
        deployed = [i for i in self._approval_queue if i.get("status") == "pr_created"]

        for item in deployed:
            health = await watcher.check_health()
            verification = {
                "item_id": item.get("id"),
                "health_score": health.get("score", 0),
                "health_status": health.get("status", "unknown"),
                "verified": health.get("score", 0) >= 70,
            }
            results.append(verification)

            if verification["verified"]:
                item["status"] = "verified"
                logger.info("Deployment verified: %s (health=%.1f)", item.get("id"), health.get("score", 0))
            else:
                item["status"] = "verification_failed"
                logger.warning(
                    "Deployment verification failed: %s (health=%.1f)", item.get("id"), health.get("score", 0)
                )

        return results

    async def learn_from_outcomes(self) -> None:
        """Record outcomes in institutional memory (Step 8: Learn)."""
        analyst = self._sub_agents.get(AgentRole.ANALYST)
        advisor = self._sub_agents.get(AgentRole.ADVISOR)

        verified = [i for i in self._approval_queue if i.get("status") in ("verified", "verification_failed")]

        for item in verified:
            success = item["status"] == "verified"

            # Record in memory
            if analyst:
                entry = (
                    f"\n### {item.get('id')}: {item.get('title', 'unknown')}\n"
                    f"- outcome: {'success' if success else 'failed'}\n"
                    f"- date: {datetime.now(timezone.utc).isoformat()}\n"
                )
                analyst.write_memory("decisions.md", entry)

            # Track pattern for crystallization
            if advisor and success:
                category = item.get("category", "general")
                advisor.track_pattern(category, success)

            # Update item status
            item["status"] = "done" if success else "needs_investigation"

    # ------------------------------------------------------------------
    # Signal Processing
    # ------------------------------------------------------------------

    def classify_signal(self, signal: dict[str, Any]) -> str:
        """Classify an incoming signal for routing."""
        signal_type = signal.get("type", "unknown")

        if signal_type in ("error", "health_check_failure", "crash"):
            return "health-issue"
        if signal_type in ("optimization", "debt", "dependency", "pattern"):
            return "improvement-opportunity"
        if signal_type in ("chat", "command", "config_change"):
            return "admin-request"
        if signal_type in ("architecture_shift", "technology_pivot", "focus_rebalance", "scope_change"):
            return "evolution-signal"

        return "unknown"

    def process_signal(self, signal: dict[str, Any]) -> dict[str, Any]:
        """Process an incoming signal and route to appropriate handler."""
        classification = self.classify_signal(signal)

        if classification == "health-issue":
            if self._emergency_mode:
                return {"action": "already_in_emergency_mode"}
            watcher = self._sub_agents.get(AgentRole.WATCHER)
            if watcher:
                result: dict[str, Any] = watcher.handle_error(
                    signal.get("signature", "unknown"),
                    signal,
                )
                return result
            return {"action": "no_watcher_registered"}

        if classification == "improvement-opportunity":
            self._signal_queue.append(signal)
            return {"action": "queued_for_next_cycle", "queue_size": len(self._signal_queue)}

        if classification == "evolution-signal":
            return self._handle_evolution_signal(signal)

        return {"action": "unhandled", "classification": classification}

    def _handle_evolution_signal(self, signal: dict[str, Any]) -> dict[str, Any]:
        """Handle an evolution/pivot signal."""
        logger.info("Evolution signal detected: %s", signal.get("category", "unknown"))

        # Record in evolution log
        evo_path = self._hive_dir / "evolution-log.md"
        if evo_path.exists():
            entry = (
                f"\n## Evolution Signal: {datetime.now(timezone.utc).isoformat()}\n"
                f"- category: {signal.get('category', 'unknown')}\n"
                f"- evidence: {signal.get('evidence', 'none')}\n"
                f"- impact: {signal.get('impact', 'unknown')}\n"
            )
            with evo_path.open("a", encoding="utf-8") as f:
                f.write(entry)

        # Trigger backlog reprioritization
        advisor = self._sub_agents.get(AgentRole.ADVISOR)
        if advisor:
            advisor.reprioritize_backlog(evolution_context=signal)

        return {"action": "evolution_signal_processed", "signal": signal}

    # ------------------------------------------------------------------
    # Emergency Mode
    # ------------------------------------------------------------------

    def enter_emergency_mode(self, reason: str) -> None:
        """Enter emergency mode -- pause all improvement cycles."""
        self._emergency_mode = True
        for cycle in self._active_cycles:
            cycle["status"] = "paused_emergency"
        logger.critical("EMERGENCY MODE ACTIVATED: %s", reason)

    def exit_emergency_mode(self) -> None:
        """Exit emergency mode -- resume normal operations."""
        self._emergency_mode = False
        logger.info("Emergency mode deactivated. Resuming normal operations.")

    # ------------------------------------------------------------------
    # Approval Queue Management
    # ------------------------------------------------------------------

    def add_to_approval_queue(self, item: dict[str, Any]) -> str:
        """Add an item to the approval queue. Returns item ID."""
        item_id = item.get("id") or f"HIVE-{len(self._approval_queue) + 1:03d}"
        item["id"] = item_id
        item["status"] = "proposed"
        item["queued_at"] = datetime.now(timezone.utc).isoformat()
        self._approval_queue.append(item)

        # Persist to .hive/backlog/items/
        self._persist_backlog_item(item)

        logger.info("Added to approval queue: %s -- %s", item_id, item.get("title", "untitled"))
        return item_id

    def approve_item(self, item_id: str) -> bool:
        """Approve a queued item."""
        for item in self._approval_queue:
            if item["id"] == item_id:
                item["status"] = "approved"
                item["approved_at"] = datetime.now(timezone.utc).isoformat()
                self._persist_backlog_item(item)
                logger.info("Approved: %s", item_id)
                return True
        return False

    def reject_item(self, item_id: str, reason: str) -> bool:
        """Reject a queued item with reason."""
        for item in self._approval_queue:
            if item["id"] == item_id:
                item["status"] = "rejected"
                item["rejected_at"] = datetime.now(timezone.utc).isoformat()
                item["reject_reason"] = reason
                self._persist_backlog_item(item)
                logger.info("Rejected: %s -- %s", item_id, reason)
                return True
        return False

    def get_approval_queue(self, status: str = "proposed") -> list[dict[str, Any]]:
        """Get items in the approval queue, filtered by status."""
        return [i for i in self._approval_queue if i["status"] == status]

    def _persist_backlog_item(self, item: dict[str, Any]) -> None:
        """Write a backlog item to .hive/backlog/items/."""
        items_dir = self._hive_dir / "backlog" / "items"
        items_dir.mkdir(parents=True, exist_ok=True)

        item_id = item["id"]
        filepath = items_dir / f"{item_id}.md"
        content = f"""# {item_id}: {item.get("title", "untitled")}

- id: {item_id}
- status: {item.get("status", "proposed")}
- title: {item.get("title", "")}
- priority: {item.get("priority", "medium")}
- created: {item.get("created", item.get("queued_at", ""))}
- source: {item.get("source", "unknown")}

## Problem
{item.get("problem", "No description.")}

## Solution
{item.get("solution", "No solution specified.")}

## Files
- Modify: {", ".join(item.get("files_to_modify", []))}
- Create: {", ".join(item.get("files_to_create", []))}

## Estimates
- Impact: {item.get("estimated_impact", "unknown")}
- Effort: {item.get("estimated_effort_days", "unknown")} days
- Risk: {item.get("risk", "unknown")}

## Rollback
{item.get("rollback_plan", "Revert the PR.")}
"""
        filepath.write_text(content, encoding="utf-8")

    # ------------------------------------------------------------------
    # Inter-Agent Communication
    # ------------------------------------------------------------------

    def route_message(self, message: AgentMessage) -> AgentMessage | None:
        """Route a message to the appropriate sub-agent."""
        target = self._sub_agents.get(message.to_agent)
        if target is None:
            logger.warning("No agent registered for role: %s", message.to_agent.value)
            return None

        if not target.is_enabled:
            logger.warning("Target agent %s is disabled", target.name)
            return None

        self.send_message(message)
        response: AgentMessage | None = target.receive_message(message)
        return response

    def receive_message(self, message: AgentMessage) -> AgentMessage | None:
        """CEO processes incoming messages by routing to appropriate handler."""
        self._message_log.append(message)

        if message.message_type == MessageType.ALERT:
            classification = self.classify_signal(message.payload)
            if classification == "health-issue":
                # Check if we should enter emergency mode
                severity = message.payload.get("severity", "")
                if severity == "critical":
                    self.enter_emergency_mode(message.payload.get("summary", "critical alert"))

                return self.route_message(
                    AgentMessage(
                        from_agent=AgentRole.CEO,
                        to_agent=AgentRole.WATCHER,
                        message_type=MessageType.TASK,
                        priority=Priority.HIGH,
                        payload=message.payload,
                    )
                )

        if message.message_type == MessageType.SIGNAL:
            self.process_signal(message.payload)

        return None

    def get_all_agent_statuses(self) -> list[dict[str, Any]]:
        """Get status from all registered sub-agents."""
        statuses = [self.get_status()]
        for agent in self._sub_agents.values():
            statuses.append(agent.get_status())
        return statuses

    def escalate_to_admin(self, context: dict[str, Any]) -> None:
        """Escalate an issue to the human admin."""
        escalation = {
            "type": "escalation",
            "level": 3,
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "context": context,
        }
        self._active_incidents.append(escalation)
        logger.warning("ESCALATION to admin: %s", context.get("summary", "unknown issue"))

    def get_status(self) -> dict[str, Any]:
        """CEO-specific status including queue and cycle counts."""
        status = super().get_status()
        status.update(
            {
                "approval_queue_size": len(self.get_approval_queue()),
                "active_incidents": len(self._active_incidents),
                "registered_agents": [r.value for r in self._sub_agents],
                "cycle_count": self._cycle_count,
                "active_cycles": len(self._active_cycles),
                "emergency_mode": self._emergency_mode,
            }
        )
        return status
