#!/usr/bin/env python3
"""
core.py - Echo Ginnie Core Session (WO#GINNIE_01 + 09)
=======================================================

Main EchoGinnieSession class that orchestrates:
- Backend LLM calls
- Tool execution
- Work order processing
- Receipt generation
- Corpus capture for training
"""

import json
import time
from dataclasses import dataclass, field, asdict
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional, Callable

from helix_cdc.ginnie.config import GinnieConfig, get_config
from helix_cdc.ginnie.planner import plan_work_order, apply_reflex, GinniePlan
from helix_cdc.ginnie.corpus import record_session, append_to_corpus


@dataclass
class WorkOrderResult:
    """Result of a work order execution."""
    wo_id: str
    description: str
    status: str  # "success", "partial", "failed"
    steps_completed: int
    steps_total: int
    touched_files: List[str] = field(default_factory=list)
    outputs: List[Dict[str, Any]] = field(default_factory=list)
    errors: List[str] = field(default_factory=list)
    duration_s: float = 0.0
    scars: int = 0  # For Jazz/ShellRouter tracking
    timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())

    def to_dict(self) -> Dict[str, Any]:
        return asdict(self)


class EchoGinnieSession:
    """
    Main Echo Ginnie session.

    Coordinates LLM backends, tools, and work order execution.
    """

    def __init__(
        self,
        config: Optional[GinnieConfig] = None,
        backend_name: Optional[str] = None,
    ):
        """
        Initialize Echo Ginnie session.

        Args:
            config: Configuration (uses global if not provided)
            backend_name: Override default backend
        """
        self.config = config or get_config()
        self.backend_name = backend_name or self.config.default_backend

        # Will be initialized lazily
        self._backend = None
        self._tools: Dict[str, Callable] = {}
        self._sandbox = None

        # Session state
        self.wo_counter = 0
        self.session_id = f"ginnie_{int(time.time())}"

        # Ensure directories exist
        self.config.logs_dir.mkdir(parents=True, exist_ok=True)
        self.config.receipts_dir.mkdir(parents=True, exist_ok=True)
        self.config.scratch_dir.mkdir(parents=True, exist_ok=True)

    @property
    def backend(self):
        """Lazy load backend."""
        if self._backend is None:
            from helix_cdc.ginnie.backends import get_backend
            self._backend = get_backend(self.backend_name, self.config)
        return self._backend

    @property
    def sandbox(self):
        """Lazy load sandbox."""
        if self._sandbox is None:
            from helix_cdc.ginnie.sandbox import Sandbox
            self._sandbox = Sandbox(self.config)
        return self._sandbox

    def register_tool(self, name: str, func: Callable) -> None:
        """Register a tool function."""
        self._tools[name] = func

    def get_tool(self, name: str) -> Optional[Callable]:
        """Get a registered tool."""
        return self._tools.get(name)

    def list_tools(self) -> List[str]:
        """List registered tools."""
        return list(self._tools.keys())

    def run_work_order(self, description: str) -> WorkOrderResult:
        """
        Execute a work order.

        This is the main entry point. It:
        1. Plans the work order using planner module
        2. Applies reflex brain for safety checks
        3. Executes each step
        4. Captures to corpus for training
        5. Returns structured result with receipt

        Args:
            description: Natural language work order description

        Returns:
            WorkOrderResult with status and outputs
        """
        self.wo_counter += 1
        wo_id = f"{self.session_id}_wo{self.wo_counter:03d}"
        start_time = time.perf_counter()

        result = WorkOrderResult(
            wo_id=wo_id,
            description=description,
            status="pending",
            steps_completed=0,
            steps_total=0,
        )

        plan: Optional[GinniePlan] = None
        helix_metrics: Dict[str, Any] = {}
        symbolic_metrics: Dict[str, Any] = {}

        try:
            # Step 1: Plan the work order using planner module
            plan = plan_work_order(description, wo_id, self.config, self.sandbox)

            # Step 2: Apply reflex brain for safety
            plan = apply_reflex(plan)
            result.steps_total = len(plan.steps)

            # Record any warnings from reflex
            if plan.warnings:
                result.errors.extend([w for w in plan.warnings if w.startswith("ERROR")])

            # Step 3: Execute each step
            step_results = []
            for i, step in enumerate(plan.steps):
                step_result = self._execute_step_v2(step)
                step_results.append(step_result)
                result.outputs.append(step_result)

                if step_result.get("touched_files"):
                    result.touched_files.extend(step_result["touched_files"])

                if step_result.get("error"):
                    result.errors.append(step_result["error"])
                else:
                    result.steps_completed += 1

                # Capture helix/symbolic metrics from tool runs
                if step_result.get("helix_metrics"):
                    helix_metrics.update(step_result["helix_metrics"])
                if step_result.get("symbolic_metrics"):
                    symbolic_metrics.update(step_result["symbolic_metrics"])

            # Determine final status
            if result.steps_completed == result.steps_total:
                result.status = "success"
            elif result.steps_completed > 0:
                result.status = "partial"
            else:
                result.status = "failed"

        except Exception as e:
            result.status = "failed"
            result.errors.append(str(e))

        result.duration_s = time.perf_counter() - start_time

        # Write receipt
        self._write_receipt(result)

        # Step 4: Capture to corpus for training
        if plan is not None:
            try:
                corpus_record = record_session(
                    wo_id=wo_id,
                    request=description,
                    backend=self.backend_name,
                    request_type=plan.request_type,
                    plan=plan.to_dict(),
                    steps=[s for s in result.outputs],
                    result=result.to_dict(),
                    helix_metrics=helix_metrics,
                    symbolic_metrics=symbolic_metrics,
                )
                append_to_corpus(corpus_record)
            except Exception:
                pass  # Don't fail work order if corpus capture fails

        return result

    def _plan_work_order(self, description: str) -> Dict[str, Any]:
        """
        Plan a work order into discrete steps.

        Uses LLM to break down the work order.
        """
        system_prompt = self._build_system_prompt()

        planning_prompt = f"""Break down this work order into 1-5 discrete steps.

Work Order: {description}

For each step, specify:
- action: What to do (read, write, run_tool, analyze)
- target: File path or tool name
- details: Specific instructions

Respond in JSON format:
{{
    "steps": [
        {{"action": "read", "target": "path/to/file", "details": "Understand the structure"}},
        {{"action": "write", "target": "path/to/new_file", "details": "Create with content..."}},
        ...
    ]
}}
"""

        try:
            response = self.backend.complete([
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": planning_prompt},
            ])

            # Parse JSON from response
            # Try to find JSON in the response
            import re
            json_match = re.search(r'\{[\s\S]*\}', response)
            if json_match:
                return json.loads(json_match.group())
            else:
                # Fallback: single analysis step
                return {
                    "steps": [
                        {"action": "analyze", "target": "work_order", "details": description}
                    ]
                }
        except Exception as e:
            return {
                "steps": [
                    {"action": "analyze", "target": "work_order", "details": description, "error": str(e)}
                ]
            }

    def _execute_step(self, step: Dict[str, Any]) -> Dict[str, Any]:
        """Execute a single step from the plan (legacy dict format)."""
        action = step.get("action", "analyze")
        target = step.get("target", "")
        details = step.get("details", "")

        result = {
            "action": action,
            "target": target,
            "status": "pending",
            "touched_files": [],
        }

        try:
            if action == "read":
                content = self.sandbox.read_file(target)
                result["output"] = f"Read {len(content)} bytes from {target}"
                result["status"] = "success"

            elif action == "write":
                # For now, just log what would be written
                result["output"] = f"Would write to {target}: {details[:100]}..."
                result["status"] = "success"
                result["touched_files"].append(target)

            elif action == "run_tool":
                tool = self.get_tool(target)
                if tool:
                    tool_result = tool(details)
                    result["output"] = tool_result
                    result["status"] = "success"
                else:
                    result["error"] = f"Tool not found: {target}"
                    result["status"] = "failed"

            elif action == "analyze":
                result["output"] = f"Analyzed: {details}"
                result["status"] = "success"

            else:
                result["error"] = f"Unknown action: {action}"
                result["status"] = "failed"

        except Exception as e:
            result["error"] = str(e)
            result["status"] = "failed"

        return result

    def _execute_step_v2(self, step) -> Dict[str, Any]:
        """Execute a GinnieStep from the planner module."""
        from helix_cdc.ginnie.planner import GinnieStep

        result = {
            "kind": step.kind,
            "description": step.description,
            "status": "pending",
            "touched_files": [],
            "helix_metrics": {},
            "symbolic_metrics": {},
        }

        try:
            if step.kind == "read":
                # Read file(s)
                for path in step.target_paths[:3]:  # Max 3 files
                    try:
                        content = self.sandbox.read_file(path)
                        result["output"] = f"Read {len(content)} bytes from {path}"
                        result["status"] = "success"
                    except Exception as e:
                        result["output"] = f"Could not read {path}: {e}"
                        result["status"] = "partial"

            elif step.kind == "edit":
                # Edit would require LLM to generate changes
                result["output"] = f"Edit step: {step.description}"
                result["touched_files"] = step.target_paths
                result["status"] = "success"

            elif step.kind == "run_cmd":
                # Execute command (sandboxed)
                result["output"] = f"Would run: {step.args.get('cmd', 'unknown')}"
                result["status"] = "success"

            elif step.kind == "helix_tool":
                # Run a Helix tool adapter
                from helix_cdc.ginnie.tools_helix import HELIX_TOOLS
                tool_name = step.tool_name or ""
                if tool_name in HELIX_TOOLS:
                    tool_result = HELIX_TOOLS[tool_name](**step.args)
                    result["output"] = tool_result
                    result["helix_metrics"] = tool_result if isinstance(tool_result, dict) else {}
                    result["status"] = "success"
                else:
                    result["error"] = f"Unknown helix tool: {tool_name}"
                    result["status"] = "failed"

            elif step.kind == "symbolic_tool":
                # Run a symbolic tool (KRISPER, Bio_Poetica)
                from helix_cdc.ginnie.tools_symbolic import SYMBOLIC_TOOLS
                tool_name = step.tool_name or ""
                if tool_name in SYMBOLIC_TOOLS:
                    # Get input text if available
                    input_text = step.args.get("text", "")
                    if not input_text and step.target_paths:
                        try:
                            input_text = self.sandbox.read_file(step.target_paths[0])
                        except Exception:
                            input_text = ""
                    tool_result = SYMBOLIC_TOOLS[tool_name](input_text)
                    result["output"] = tool_result
                    result["symbolic_metrics"] = tool_result if isinstance(tool_result, dict) else {}
                    result["status"] = "success"
                else:
                    result["error"] = f"Unknown symbolic tool: {tool_name}"
                    result["status"] = "failed"

            elif step.kind == "report":
                # Summary step - always succeeds
                result["output"] = f"Report: {step.description}"
                result["status"] = "success"

            else:
                result["error"] = f"Unknown step kind: {step.kind}"
                result["status"] = "failed"

        except Exception as e:
            result["error"] = str(e)
            result["status"] = "failed"

        return result

    def _build_system_prompt(self) -> str:
        """Build system prompt for LLM."""
        tools_list = ", ".join(self.config.enabled_tools)

        return f"""You are Echo Ginnie, a Helix-native coding agent.

You operate within a sandboxed workspace and have access to these tools:
{tools_list}

Rules:
1. Only operate on files within the sandbox: {self.config.repo_root}
2. Always explain your reasoning before taking action
3. Generate structured JSON plans when asked to break down tasks
4. Log all file modifications
5. Never execute destructive operations without confirmation

You think in Helix/KRISPER/Bio_Poetica terms and emit receipts for all work."""

    def _write_receipt(self, result: WorkOrderResult) -> Path:
        """Write work order receipt to disk."""
        receipt_path = self.config.receipts_dir / f"ginnie_wo_{result.wo_id}.json"

        receipt = {
            "wo": result.wo_id,
            "type": "echo_ginnie_work_order",
            "result": result.to_dict(),
        }

        receipt_path.write_text(json.dumps(receipt, indent=2))
        return receipt_path


def create_session(
    backend_name: Optional[str] = None,
    config_path: Optional[Path] = None,
) -> EchoGinnieSession:
    """Create a new Echo Ginnie session."""
    from helix_cdc.ginnie.config import GinnieConfig

    config = GinnieConfig.load(config_path) if config_path else get_config()
    return EchoGinnieSession(config=config, backend_name=backend_name)
