"""
Plan Agent - Generate work orders before execution

WO-ECHO-PLAN-AGENT-01: Agent for generating structured plans with safety checks.

Features:
- Classifies request type (read_only, modify_code, run_bench, etc.)
- Generates 3-7 step plans
- Applies reflex safety checks (forbid rm -rf, require tests after edits)
- Identifies which agents/tools each step requires
- Produces receipts with chain-hash

This is the "think before you act" organ that makes Echo coherent over
multi-step tasks.

Usage:
    from helix_cdc.echo.agents.plan_agent import PlanAgent
    from helix_cdc.echo.agents import register_agent

    register_agent(PlanAgent())
"""

import asyncio
import hashlib
import json
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

from . import (
    AgentResult,
    DispatchableAgent,
    PROJECT_ROOT,
    generate_agent_id,
)

from helix_cdc.echo.ops.helpers import generate_receipt_id

# Import planning infrastructure
from helix_cdc.ginnie.planner import (
    GinniePlan,
    GinnieStep,
    PlanReflexConfig,
    classify_request,
    plan_work_order,
    apply_reflex,
    sanity_check_plan,
)
from helix_cdc.ginnie.config import GinnieConfig


# Agent type to step kind mapping
STEP_KIND_TO_AGENT = {
    "read": "codebase",      # CodebaseAgent for reading
    "edit": "patch",         # PatchAgent for modifications
    "run_cmd": "test",       # TestAgent for command execution
    "helix_tool": "test",    # TestAgent wraps make/tool calls
    "symbolic_tool": None,   # No agent yet for symbolic tools
    "report": None,          # Synthesis, not an agent
}

# Keywords for plan intent detection
PLAN_KEYWORDS = {
    "plan", "work order", "wo", "steps", "how should", "what steps",
    "design", "approach", "strategy", "break down", "decompose",
}

PLAN_PATTERNS = [
    r"plan\s+to\s+",
    r"create\s+a?\s*plan",
    r"work\s*order",
    r"what\s+steps",
    r"how\s+should\s+i",
    r"break\s+(this\s+)?down",
    r"design\s+(a\s+)?plan",
]


class PlanAgent(DispatchableAgent):
    """
    Agent for generating structured work order plans.

    The PlanAgent is the "think before you act" organ. Given a task request,
    it produces a structured plan with:
    - Steps (each with kind, description, target paths, tool)
    - Agent mapping (which Echo agent handles each step)
    - Safety warnings (from reflex checks)
    - Verification criteria

    Safety:
    - Applies reflex brain to catch unsafe plans
    - Forbids rm -rf, sudo, path traversal
    - Requires test step after code modifications
    """

    @property
    def agent_type(self) -> str:
        return "plan"

    def __init__(
        self,
        receipt_dir: str = "receipts/plan_agent",
        reflex_config: Optional[PlanReflexConfig] = None,
    ):
        """
        Initialize the plan agent.

        Args:
            receipt_dir: Directory for receipt storage (relative to PROJECT_ROOT)
            reflex_config: Safety configuration for plan checks
        """
        self.receipt_dir = PROJECT_ROOT / receipt_dir
        self.reflex_config = reflex_config or PlanReflexConfig()
        self._ginnie_config = None

    def available(self) -> bool:
        """Check if agent is available."""
        try:
            # Check if ginnie planner is importable
            from helix_cdc.ginnie.planner import plan_work_order
            return True
        except ImportError:
            return False

    def _get_ginnie_config(self) -> GinnieConfig:
        """Get or create GinnieConfig."""
        if self._ginnie_config is None:
            self._ginnie_config = GinnieConfig()
        return self._ginnie_config

    def _generate_wo_id(self) -> str:
        """Generate a work order ID."""
        timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
        suffix = generate_receipt_id()[:6]
        return f"WO-PLAN-{timestamp}-{suffix}"

    def _map_steps_to_agents(self, plan: GinniePlan) -> List[Dict[str, Any]]:
        """
        Map plan steps to Echo agents.

        Returns list of step dicts with agent mapping.
        """
        mapped_steps = []

        for i, step in enumerate(plan.steps):
            agent = STEP_KIND_TO_AGENT.get(step.kind)

            mapped_step = {
                "step_number": i + 1,
                "kind": step.kind,
                "description": step.description,
                "target_paths": step.target_paths,
                "tool_name": step.tool_name,
                "args": step.args,
                "priority": step.priority,
                "agent": agent,
                "agent_available": self._check_agent_available(agent),
            }
            mapped_steps.append(mapped_step)

        return mapped_steps

    def _check_agent_available(self, agent_type: Optional[str]) -> bool:
        """Check if an agent type is available."""
        if agent_type is None:
            return False

        from helix_cdc.echo.agents import AGENT_REGISTRY
        return agent_type in AGENT_REGISTRY and AGENT_REGISTRY[agent_type].available()

    def _compute_chain_hash(
        self,
        request_sha: str,
        plan_sha: str,
        warnings_sha: str,
        status: str,
    ) -> str:
        """
        Compute chain hash for plan receipt.

        chain_hash = sha256(request_sha || plan_sha || warnings_sha || status)
        """
        data = f"{request_sha}|{plan_sha}|{warnings_sha}|{status}"
        return hashlib.sha256(data.encode()).hexdigest()

    def _build_receipt(
        self,
        query: str,
        wo_id: str,
        plan: GinniePlan,
        mapped_steps: List[Dict[str, Any]],
        status: str,
        error: Optional[str] = None,
    ) -> Dict[str, Any]:
        """Build receipt for plan generation."""
        receipt_id = generate_receipt_id()
        timestamp = datetime.now(timezone.utc)

        # Compute hashes
        request_sha = hashlib.sha256(query.encode()).hexdigest()
        plan_json = json.dumps(plan.to_dict(), sort_keys=True)
        plan_sha = hashlib.sha256(plan_json.encode()).hexdigest()
        warnings_json = json.dumps(plan.warnings, sort_keys=True)
        warnings_sha = hashlib.sha256(warnings_json.encode()).hexdigest()

        chain_hash = self._compute_chain_hash(
            request_sha, plan_sha, warnings_sha, status
        )

        receipt = {
            # Schema version for forward compatibility
            "schema_version": "1.0.0",

            "receipt_id": receipt_id,
            "receipt_type": "plan_generation",
            "timestamp_utc": timestamp.isoformat(),
            "work_order": "WO-ECHO-PLAN-AGENT-01",

            "query": query[:500],
            "query_sha256": request_sha,

            "wo_id": wo_id,
            "request_type": plan.request_type,
            "step_count": len(plan.steps),
            "steps_preview": [
                f"{s['step_number']}. [{s['kind']}] {s['description'][:50]}"
                for s in mapped_steps[:5]
            ],

            "plan_sha256": plan_sha,
            "warnings": plan.warnings,
            "warnings_sha256": warnings_sha,
            "notes": plan.notes,
            "reflex_applied": plan.reflex_applied,

            "agents_required": list(set(
                s["agent"] for s in mapped_steps if s["agent"]
            )),
            "agents_available": [
                s["agent"] for s in mapped_steps
                if s["agent"] and s["agent_available"]
            ],

            "status": status,
            "chain_hash": chain_hash,

            # Self-documenting verification formulas
            "verification": {
                "chain_hash_formula": "sha256(query_sha256 | plan_sha256 | warnings_sha256 | status)",
                "separator": "|",
            },
        }

        if error:
            receipt["error"] = error

        return receipt

    def _save_receipt(self, receipt: Dict[str, Any]) -> str:
        """Save receipt to disk."""
        self.receipt_dir.mkdir(parents=True, exist_ok=True)

        timestamp = datetime.now(timezone.utc)
        date_str = timestamp.strftime("%Y%m%d")
        receipt_id = receipt.get("receipt_id", generate_receipt_id())[:8]
        status = receipt.get("status", "UNKNOWN").lower()

        filename = f"{status}_{date_str}_{receipt_id}.json"
        receipt_path = self.receipt_dir / filename

        with open(receipt_path, "w") as f:
            json.dump(receipt, f, indent=2, default=str)

        # Try to return relative path, fall back to absolute if outside PROJECT_ROOT
        try:
            return str(receipt_path.relative_to(PROJECT_ROOT))
        except ValueError:
            return str(receipt_path)

    def _extract_verification_criteria(self, plan: GinniePlan) -> List[str]:
        """Extract verification criteria from plan."""
        criteria = []

        for step in plan.steps:
            if step.kind == "helix_tool" and step.tool_name:
                if "test" in step.tool_name.lower() or "check" in step.tool_name.lower():
                    criteria.append(f"Run {step.tool_name} → expect PASS")
            elif step.kind == "run_cmd":
                criteria.append(f"Execute command → exit code 0")

        if not criteria:
            criteria.append("Manual verification required")

        return criteria

    async def execute(self, query: str, context: Dict[str, Any]) -> AgentResult:
        """
        Generate a work order plan for the given query.

        Args:
            query: The task/request to plan
            context: Additional context (force_type, constraints, etc.)

        Returns:
            AgentResult with plan details and receipt
        """
        start_time = time.perf_counter()
        agent_id = generate_agent_id()

        if not self.available():
            return AgentResult(
                agent_id=agent_id,
                agent_type=self.agent_type,
                query=query,
                result={"status": "ERROR", "error": "PlanAgent not available"},
                sources=[],
                confidence=0.0,
                elapsed_ms=0.0,
                error="Ginnie planner not available",
            )

        try:
            # Generate work order ID
            wo_id = context.get("wo_id") or self._generate_wo_id()

            # Get config
            ginnie_config = self._get_ginnie_config()

            # Generate plan
            plan = plan_work_order(
                request=query,
                wo_id=wo_id,
                config=ginnie_config,
            )

            # Apply reflex safety checks
            plan = apply_reflex(plan, self.reflex_config)

            # Map steps to agents
            mapped_steps = self._map_steps_to_agents(plan)

            # Extract verification criteria
            verification = self._extract_verification_criteria(plan)

            # Determine status
            has_errors = any(w.startswith("ERROR") for w in plan.warnings)
            status = "FAIL" if has_errors else "PASS"

            # Build receipt
            receipt = self._build_receipt(
                query=query,
                wo_id=wo_id,
                plan=plan,
                mapped_steps=mapped_steps,
                status=status,
            )
            receipt_path = self._save_receipt(receipt)

            elapsed_ms = (time.perf_counter() - start_time) * 1000

            # Build result
            result_data = {
                "status": status,
                "wo_id": wo_id,
                "request_type": plan.request_type,
                "step_count": len(plan.steps),
                "steps": mapped_steps,
                "warnings": plan.warnings,
                "notes": plan.notes,
                "reflex_applied": plan.reflex_applied,
                "verification": verification,
                "constraints": {
                    "max_steps": self.reflex_config.max_steps,
                    "forbid_rm_rf": self.reflex_config.forbid_rm_rf,
                    "forbid_sudo": self.reflex_config.forbid_sudo,
                    "require_tests_for_code_change": self.reflex_config.require_tests_for_code_change,
                },
                "receipt_path": receipt_path,
                "chain_hash": receipt.get("chain_hash"),
            }

            # Confidence based on warnings
            if has_errors:
                confidence = 0.3
            elif plan.warnings:
                confidence = 0.7
            else:
                confidence = 1.0

            return AgentResult(
                agent_id=agent_id,
                agent_type=self.agent_type,
                query=query,
                result=result_data,
                sources=[f"receipt:{receipt_path}"],
                confidence=confidence,
                elapsed_ms=elapsed_ms,
                error=None if status == "PASS" else "Plan has safety warnings",
            )

        except Exception as e:
            elapsed_ms = (time.perf_counter() - start_time) * 1000
            error_msg = str(e)

            # Build error receipt
            receipt = {
                "receipt_id": generate_receipt_id(),
                "receipt_type": "plan_generation",
                "timestamp_utc": datetime.now(timezone.utc).isoformat(),
                "work_order": "WO-ECHO-PLAN-AGENT-01",
                "query": query[:500],
                "query_sha256": hashlib.sha256(query.encode()).hexdigest(),
                "status": "ERROR",
                "error": error_msg[:500],
                "chain_hash": hashlib.sha256(f"ERROR:{error_msg}".encode()).hexdigest(),
            }
            receipt_path = self._save_receipt(receipt)

            return AgentResult(
                agent_id=agent_id,
                agent_type=self.agent_type,
                query=query,
                result={
                    "status": "ERROR",
                    "error": error_msg,
                    "receipt_path": receipt_path,
                },
                sources=[f"receipt:{receipt_path}"],
                confidence=0.0,
                elapsed_ms=elapsed_ms,
                error=error_msg,
            )


def detect_plan_intent(prompt: str) -> Dict[str, Any]:
    """
    Detect if a prompt requires planning.

    Returns:
        dict with:
            - is_plan: bool - True if planning should be used
            - matched_keywords: list - Keywords that matched
            - matched_patterns: list - Regex patterns that matched
            - confidence: float (0-1) - Confidence score
            - suggested_tool: str or None - Tool to route to
    """
    import re

    prompt_lower = prompt.lower()

    # Check keywords
    matched_keywords = []
    for kw in PLAN_KEYWORDS:
        if kw in prompt_lower:
            matched_keywords.append(kw)

    # Check patterns
    matched_patterns = []
    for pattern in PLAN_PATTERNS:
        if re.search(pattern, prompt_lower):
            matched_patterns.append(pattern)

    # Compute confidence
    keyword_score = len(matched_keywords) * 0.15
    pattern_score = len(matched_patterns) * 0.30
    confidence = min(1.0, keyword_score + pattern_score)

    # Determine if plan
    is_plan = len(matched_patterns) >= 1 or len(matched_keywords) >= 2

    return {
        "is_plan": is_plan,
        "matched_keywords": matched_keywords,
        "matched_patterns": matched_patterns,
        "confidence": round(confidence, 3),
        "suggested_tool": "plan_agent" if is_plan else None,
    }


# Auto-register the agent when this module is imported
from . import register_agent as _register

try:
    _register(PlanAgent())
except ValueError:
    pass  # Already registered
