"""Main XYGLUE Engine — orchestrates thinking, execution, healing, and capture."""

import time
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, cast
from uuid import UUID, uuid4

from xyglue.config import EngineConfig
from xyglue.goal.processor import GoalProcessor
from xyglue.thinking.scout import Scout
from xyglue.thinking.physicist import Physicist
from xyglue.thinking.architect import Architect
from xyglue.thinking.models import ThinkingResult, Path
from xyglue.plan.models import Plan, Step
from xyglue.plan.checkpoint import CheckpointManager
from xyglue.execute.executor import Executor
from xyglue.execute.models import ExecutionResult, ExecutionContext
from xyglue.heal.healer import Healer
from xyglue.learn.patterns import PatternLibrary
from xyglue.learn.seeds import seed_patterns
from xyglue.capture.ground_tracker import GroundTracker
from xyglue.capture.templates import TemplateLibrary
from xyglue.brief.brief import Brief, build_brief
from xyglue.tools.base import Tool


@dataclass
class RunResult:
    id: UUID = field(default_factory=uuid4)
    status: str = "success"  # success | partial | failed | pending
    path_used: str = ""
    output: Any = None
    brief: Brief = field(default_factory=Brief)
    quality_score: float = 0.0
    was_healed: bool = False
    heal_count: int = 0
    paths_attempted: list[str] = field(default_factory=list)
    template_status: str = ""
    thinking_result: ThinkingResult | None = None
    execution_results: list[ExecutionResult] = field(default_factory=list)
    duration_ms: int = 0
    created_at: datetime = field(default_factory=datetime.utcnow)

    def to_dict(self) -> dict[str, Any]:
        return {
            "id": str(self.id),
            "status": self.status,
            "path_used": self.path_used,
            "quality_score": self.quality_score,
            "was_healed": self.was_healed,
            "heal_count": self.heal_count,
            "paths_attempted": self.paths_attempted,
            "template_status": self.template_status,
            "duration_ms": self.duration_ms,
        }


class XyglueEngine:
    """
    Full pipeline:
    1. Process goal
    2. THINKING: Scout -> Physicist -> Architect -> 3 Paths
    3. Brief (pause if semi)
    4. EXECUTE: Path A -> heal/fallthrough -> B -> heal/fallthrough -> C
    5. CAPTURE: Ground tracker + template validation
    6. Return RunResult with Brief
    """

    def __init__(self, config: EngineConfig, ai_client: Any = None) -> None:
        self.config = config
        self.ai_client = ai_client

        self.goal_processor = GoalProcessor()

        self.scout = Scout(
            name=config.agent_names.get("scout", "Scout"),
            ai_client=ai_client,
            model=config.ai_model,
        )
        self.physicist = Physicist(
            name=config.agent_names.get("physicist", "Physicist"),
            ai_client=ai_client,
            model=config.ai_model,
        )
        self.architect = Architect(
            name=config.agent_names.get("architect", "Architect"),
            ai_client=ai_client,
            model=config.ai_model,
        )

        self.executor = Executor()
        self.checkpoint_manager = CheckpointManager()

        self.pattern_library = PatternLibrary(
            max_patterns=config.max_patterns,
            min_confidence=config.min_confidence_to_keep,
            decay_rate=config.decay_rate_per_day,
        )
        seed_patterns(self.pattern_library)

        self.healer = Healer(
            pattern_library=self.pattern_library,
            ai_client=ai_client,
            max_heals_per_step=config.max_heals_per_step,
        )

        self.ground_tracker = GroundTracker()
        self.template_library = TemplateLibrary(
            quality_threshold=config.template_quality_threshold,
            proven_count=config.template_proven_count,
            failure_resets=config.template_failure_resets,
        )

    def register_tool(self, tool: Tool) -> None:
        self.executor.register_tool(tool)

    async def run(
        self,
        goal_input: str,
        tools: list[str] | None = None,
        context: dict[str, Any] | None = None,
        mode: str | None = None,
        divergence: str | None = None,
        path: str | None = None,
    ) -> RunResult:
        run_id = uuid4()
        start = time.time()
        run_mode = cast(Any, mode or self.config.mode)
        run_config = EngineConfig(
            mode=run_mode,
            divergence=cast(Any, divergence or self.config.divergence),
            learning=self.config.learning,
            agent_names=self.config.agent_names,
            template_quality_threshold=self.config.template_quality_threshold,
            template_proven_count=self.config.template_proven_count,
            ai_model=self.config.ai_model,
        )
        available_tools = tools or self.config.allowed_tools

        # STEP 1: Process Goal
        goal = await self.goal_processor.process(goal_input, context=context)

        # STEP 2: Thinking Layer
        prior_templates = self.template_library.get_template_summaries(
            goal.context.get("domain", "general")
        )
        benchmark_map = await self.scout.analyze(
            goal, available_tools, prior_templates
        )
        classified_map = await self.physicist.classify(
            benchmark_map, available_tools
        )
        paths = await self.architect.design(
            classified_map, available_tools, run_config
        )

        thinking_duration = int((time.time() - start) * 1000)
        thinking_result = ThinkingResult(
            goal_text=goal.normalized,
            benchmark_map=benchmark_map,
            classified_map=classified_map,
            paths=paths,
            recommended_path="A",
            thinking_duration_ms=thinking_duration,
        )

        # STEP 3: Brief
        brief = build_brief(thinking_result, run_config)

        # Semi mode: return with brief, user decides
        if run_mode == "semi" and path is None:
            return RunResult(
                id=run_id,
                status="pending",
                brief=brief,
                thinking_result=thinking_result,
                duration_ms=int((time.time() - start) * 1000),
            )

        # STEP 4: Execute
        target_path = path or "A"
        path_order = ["A", "B", "C"]
        if target_path in path_order:
            path_order = path_order[path_order.index(target_path) :]

        all_results: list[ExecutionResult] = []
        paths_attempted: list[str] = []
        winning_path = ""
        was_healed = False
        total_heals = 0
        failed_learnings: list[dict] = []

        for pid in path_order:
            path_obj = thinking_result.get_path(pid)
            if not path_obj:
                continue
            paths_attempted.append(pid)
            plan = self._path_to_plan(path_obj, goal.id)
            results, healed, heals = await self._execute_with_healing(
                run_id, plan
            )
            all_results.extend(results)
            if healed:
                was_healed = True
            total_heals += heals

            if results and all(r.success for r in results):
                winning_path = pid
                break
            else:
                failed_learnings.append(
                    {
                        "path_id": pid,
                        "failed_at_step": next(
                            (r.step_number for r in results if r.failed), -1
                        ),
                        "error": next(
                            (r.error for r in results if r.failed), "unknown"
                        ),
                    }
                )

        # STEP 5: Score
        quality_score = self._score_quality(all_results, winning_path)

        # STEP 6: Capture
        self.ground_tracker.record(
            run_id=run_id,
            thinking_result=thinking_result,
            winning_path=winning_path,
            paths_attempted=paths_attempted,
            quality_score=quality_score,
            was_healed=was_healed,
            heal_count=total_heals,
            failed_learnings=failed_learnings,
        )

        template_status = ""
        if winning_path and quality_score >= self.config.template_quality_threshold:
            if self.config.learning == "auto":
                template_status = self.template_library.submit_candidate(
                    thinking_result, winning_path, quality_score
                )
            else:
                template_status = "awaiting_approval"

        # STEP 7: Finalize
        status = "success" if winning_path else "failed"
        brief.execution_status = status
        brief.path_used = winning_path
        brief.was_healed = was_healed
        brief.paths_attempted = paths_attempted
        brief.template_status = template_status

        output = None
        if all_results:
            successful = [r for r in all_results if r.success]
            if successful:
                output = successful[-1].output

        return RunResult(
            id=run_id,
            status=status,
            path_used=winning_path,
            output=output,
            brief=brief,
            quality_score=quality_score,
            was_healed=was_healed,
            heal_count=total_heals,
            paths_attempted=paths_attempted,
            template_status=template_status,
            thinking_result=thinking_result,
            execution_results=all_results,
            duration_ms=int((time.time() - start) * 1000),
        )

    async def approve_and_execute(
        self, pending: RunResult, path: str = "A"
    ) -> RunResult:
        if pending.status != "pending" or not pending.thinking_result:
            raise ValueError("Can only approve pending results")
        return await self.run(
            pending.thinking_result.goal_text, mode="auto", path=path
        )

    async def _execute_with_healing(
        self, run_id: UUID, plan: Plan
    ) -> tuple[list[ExecutionResult], bool, int]:
        results: list[ExecutionResult] = []
        was_healed = False
        heal_count = 0
        for step in plan.steps:
            self.healer.reset_for_step()
            ctx = ExecutionContext(
                run_id=run_id,
                step_number=step.number,
                previous_results=results,
            )
            result = await self.executor.execute_step(step, ctx)
            if result.failed and self.healer.can_heal():
                heal_result = await self.healer.heal(run_id, step, result)
                if heal_result.success and heal_result.fix_applied:
                    was_healed = True
                    heal_count += 1
                    result = await self.executor.execute_step(step, ctx)
                    self.healer.record_fix_outcome(
                        heal_result.fix_applied, result.success
                    )
            results.append(result)
            if result.failed:
                break
        return results, was_healed, heal_count

    def _path_to_plan(
        self, path: Path, goal_id: UUID | None = None
    ) -> Plan:
        steps = [
            Step.from_dict(s) if isinstance(s, dict) else s for s in path.steps
        ]
        steps = self.checkpoint_manager.enrich_plan_checkpoints(steps)
        return Plan(
            goal_id=goal_id,
            path_id=path.id,
            steps=steps,
            tools_required=path.tools_required,
            estimated_duration_ms=path.estimated_duration_ms,
        )

    def _score_quality(
        self, results: list[ExecutionResult], winning_path: str
    ) -> float:
        if not results or not winning_path:
            return 0.0
        base = sum(1 for r in results if r.success) / len(results)
        if winning_path == "A":
            base = min(1.0, base + 0.05)
        return round(base, 4)
