"""Cognitive agent loop."""

from __future__ import annotations

import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from .bytecode import CognitiveOp, Instruction
from .controller import Controller, HeuristicController
from .memory import EpisodeMemory
from .skills import SkillGraph
from .trace import Episode, TraceRecorder
from .verifiers import StringContainsVerifier
from .workspace import NodeType, Workspace


@dataclass(slots=True)
class AgentRunResult:
    workspace: Workspace
    episode: Episode
    answer: str
    trace_length: int
    memory_path: Path
    skills_path: Path


class CognitiveAgent:
    def __init__(
        self,
        memory: EpisodeMemory | None = None,
        skills: SkillGraph | None = None,
        controller: Controller | None = None,
    ) -> None:
        temp_dir = Path(tempfile.gettempdir()) / "ra_cpu"
        temp_dir.mkdir(parents=True, exist_ok=True)
        self.memory = memory or EpisodeMemory(temp_dir / "episodes.sqlite3")
        self.skills = skills or SkillGraph(temp_dir / "skills.json")
        self.controller = controller or HeuristicController()

    def run(self, task: str, max_steps: int = 12) -> AgentRunResult:
        workspace = Workspace.from_task(task)
        recorder = TraceRecorder(task)
        answer = ""

        for _ in range(max_steps):
            instruction = self.controller.next_instruction(workspace)
            success, summary, data = self.execute(instruction, workspace)
            recorder.record(instruction, summary, success, data=data)
            workspace.increment_step()
            if instruction.op == CognitiveOp.ANSWER:
                answer = summary
                break

        if not answer:
            answer = "Budget exhausted before final answer. Stored as failure."
            outcome = "failure"
            score = 0.0
        else:
            outcome = "success"
            score = 1.0

        episode = recorder.to_episode(
            outcome=outcome,
            score=score,
            tags=self._tags_for_task(task),
            metadata={"workspace_summary": workspace.summary()},
        )
        self.memory.add_episode(episode)
        return AgentRunResult(
            workspace=workspace,
            episode=episode,
            answer=answer,
            trace_length=len(episode.steps),
            memory_path=self.memory.path,
            skills_path=self.skills.path,
        )

    def execute(self, instruction: Instruction, workspace: Workspace) -> tuple[bool, str, dict[str, Any]]:
        op = instruction.op

        if op == CognitiveOp.PARSE_INPUT:
            workspace.set_stage("parsed")
            workspace.add_node(NodeType.EVIDENCE, "task_text_available", {"task": workspace.task})
            return True, "Parsed input into task evidence.", {}

        if op == CognitiveOp.CLASSIFY_TASK:
            task_type = self._classify(workspace.task)
            workspace.set_stage("classified")
            workspace.metadata["task_type"] = task_type
            workspace.add_node(NodeType.CONSTRAINT, "mvp_runtime_scope", {"task_type": task_type})
            return True, f"Classified task as {task_type}.", {"task_type": task_type}

        if op == CognitiveOp.CREATE_GOAL:
            workspace.set_stage("goal_created")
            goal = workspace.add_node(NodeType.GOAL, f"Solve task: {workspace.task}")
            task_nodes = workspace.find(NodeType.TASK)
            if task_nodes:
                workspace.add_edge(task_nodes[0], goal, "defines")
            return True, "Created primary goal.", {"goal": goal.label}

        if op == CognitiveOp.RETRIEVE_MEMORY:
            query = str(instruction.args.get("query") or workspace.task)
            hits = self.memory.search(query, limit=3)
            workspace.set_stage("memory_retrieved")
            workspace.metadata["memory_hit_count"] = len(hits)
            for hit in hits:
                workspace.add_node(
                    NodeType.MEMORY,
                    f"memory:{hit.episode.episode_id}",
                    {"score": hit.score, "task": hit.episode.task, "outcome": hit.episode.outcome},
                    confidence=min(1.0, hit.score),
                )
            return True, f"Retrieved {len(hits)} memory hit(s).", {"hit_count": len(hits)}

        if op == CognitiveOp.RETRIEVE_SKILL:
            query = str(instruction.args.get("query") or workspace.task)
            matches = self.skills.match(query, limit=3)
            promoted_matches = [match for match in matches if match.skill.promoted]
            workspace.set_stage("skill_retrieved")
            workspace.metadata["skill_match_count"] = len(matches)
            workspace.metadata["promoted_skill_match_count"] = len(promoted_matches)
            for match in matches:
                workspace.add_node(
                    NodeType.SKILL,
                    match.skill.name,
                    {"score": match.score, "promoted": match.skill.promoted},
                    confidence=min(1.0, match.score),
                )
            return True, f"Retrieved {len(matches)} skill match(es).", {"match_count": len(matches)}

        if op == CognitiveOp.CREATE_HYPOTHESIS:
            workspace.set_stage("hypothesis_created")
            hypothesis = workspace.add_node(
                NodeType.HYPOTHESIS,
                "Use bytecode + memory + skills + verifiers before neural training.",
                confidence=0.72,
            )
            goals = workspace.find(NodeType.GOAL)
            if goals:
                workspace.add_edge(hypothesis, goals[0], "supports")
            return True, "Created operational hypothesis.", {"confidence": hypothesis.confidence}

        if op == CognitiveOp.CALL_TOOL:
            operation = str(instruction.args.get("operation", ""))
            if operation == "inspect_workspace":
                return True, "Inspected workspace summary.", {"workspace_summary": workspace.summary()}
            if operation == "list_files":
                files = sorted(str(item) for item in workspace.metadata.get("observed_paths", []) or [])
                sandbox_files = workspace.metadata.get("sandbox_files")
                if isinstance(sandbox_files, dict):
                    files = sorted(set(files) | {str(path) for path in sandbox_files})
                return True, f"Listed {len(files)} observed file(s).", {"files": files}
            if operation == "read_file":
                path = str(instruction.args.get("path", ""))
                if not self._safe_relative_path(path):
                    return False, "Read-only file request rejected as unsafe.", {"path": path}
                sandbox_files = workspace.metadata.get("sandbox_files")
                if not isinstance(sandbox_files, dict) or path not in sandbox_files:
                    return False, "Read-only file request rejected because path was not observed.", {"path": path}
                content = str(sandbox_files[path])
                return True, f"Read observed file {path}.", {"path": path, "bytes": len(content), "content_preview": content[:120]}
            return False, f"Read-only tool operation {operation} is not allowlisted.", {"operation": operation}

        if op == CognitiveOp.VERIFY:
            verifier = StringContainsVerifier(text=workspace.task.lower(), expected="")
            result = verifier.verify()
            workspace.set_stage("verified")
            workspace.add_node(NodeType.RESULT, "verification_result", result.to_dict(), confidence=1.0 if result.passed else 0.0)
            return result.passed, "Verification gate passed." if result.passed else "Verification gate failed.", result.to_dict()

        if op == CognitiveOp.ANSWER:
            workspace.set_stage("answered")
            memory_count = len(workspace.find(NodeType.MEMORY))
            skill_count = len(workspace.find(NodeType.SKILL))
            hypothesis_count = len(workspace.find(NodeType.HYPOTHESIS))
            answer = (
                "MVP runtime completed: task parsed, goal created, "
                f"memory_hits={memory_count}, skill_matches={skill_count}, "
                f"hypotheses={hypothesis_count}, verifier gate executed."
            )
            workspace.add_node(NodeType.ANSWER, answer)
            return True, answer, {"memory_hits": memory_count, "skill_matches": skill_count}

        if op == CognitiveOp.STORE_EPISODE:
            return True, "Episode storage is handled after run completion.", {}

        return False, f"Operation {op.value} is not implemented in the MVP executor.", {}

    @staticmethod
    def _classify(task: str) -> str:
        text = task.lower()
        if any(word in text for word in ["pytest", "test", "bug", "debug", "traceback"]):
            return "coding_verification"
        if any(word in text for word in ["research", "paper", "claim"]):
            return "research_verification"
        return "general_planning"

    @staticmethod
    def _tags_for_task(task: str) -> list[str]:
        tags = []
        lower = task.lower()
        for candidate in ["pytest", "debug", "coding", "research", "planning", "memory", "skill"]:
            if candidate in lower:
                tags.append(candidate)
        return tags or ["general"]

    @staticmethod
    def _safe_relative_path(path: str) -> bool:
        requested = Path(path)
        return bool(path) and not requested.is_absolute() and ".." not in requested.parts
