"""Open Agent - Self-evolving personal assistant with hierarchical memory.

This module provides the OpenAgent, a minimal, self-evolving, user-driven
personal assistant that writes itself based on user needs rather than
having heavy predefined opinions.

Features hierarchical memory:
- Core Identity: Fundamental user traits (role, values, goals)
- Long-term Memory: Learned preferences and patterns
- Short-term Memory: Recent context and session state
- Skill Memory: Dynamic skills and rules learned from interactions
"""

from typing import Optional, TYPE_CHECKING
from pathlib import Path

from ..base import BaseAgent
from ..events import AgentEventEmitter
from ..providers.factory import DEFAULT_MODEL
from .toolkit import OpenToolkit
from .persona import PersonaManager
from .capabilities import CapabilityRegistry
from .prompts import build_open_system_prompt
from .memory import HierarchicalMemory
from .memory_tools import MemoryToolSet
from .skill_memory import SkillMemoryManager

if TYPE_CHECKING:
    from ..toolkits.base import BaseToolkit


class OpenAgent(BaseAgent):
    """Self-evolving personal assistant with hierarchical memory.

    Key characteristics:
    - Minimal bootstrap prompt (<100 tokens base)
    - Hierarchical memory (core → long-term → short-term)
    - Learns user preferences over time
    - Discovers and adds capabilities dynamically
    - User-driven evolution (not opinionated)
    - Self-modifying through learning tools
    """

    # Agent identity
    name = "Open Assistant"
    agent_type = "open"
    description = "A minimal, self-evolving personal assistant that learns from you"

    def __init__(
        self,
        model: str = DEFAULT_MODEL,
        emitter: Optional[AgentEventEmitter] = None,
        max_iterations: int = 100,
        verbose: bool = False,
        enable_thinking: Optional[bool] = None,
        session_id: Optional[str] = None,
        repo_root: Optional[Path] = None,
        persona_manager: Optional[PersonaManager] = None,
        capability_registry: Optional[CapabilityRegistry] = None,
        hierarchical_memory: Optional[HierarchicalMemory] = None,
        skill_memory_manager: Optional[SkillMemoryManager] = None,
    ):
        """Initialize the Open Agent.

        Args:
            model: LLM model to use
            emitter: Event emitter for streaming output
            max_iterations: Maximum tool iterations per run
            verbose: Enable verbose logging
            enable_thinking: Enable extended thinking
            session_id: Session ID for isolation
            repo_root: Repository root path
            persona_manager: Optional pre-configured PersonaManager
            capability_registry: Optional pre-configured CapabilityRegistry
            hierarchical_memory: Optional pre-configured HierarchicalMemory
            skill_memory_manager: Optional pre-configured SkillMemoryManager
        """
        # Store fields before calling super() since _get_toolkit() needs them
        self._session_id = session_id
        self._persona_manager = persona_manager
        self._capability_registry = capability_registry
        self._hierarchical_memory = hierarchical_memory
        self._skill_memory_manager = skill_memory_manager
        self._repo_root = repo_root or Path.cwd()
        self._memory_tools: Optional[MemoryToolSet] = None

        # Call parent init (which calls _get_toolkit())
        super().__init__(
            model=model,
            emitter=emitter,
            max_iterations=max_iterations,
            verbose=verbose,
            enable_thinking=enable_thinking,
            session_id=session_id,
        )

    def _get_toolkit(self) -> "BaseToolkit":
        """Return the Open Toolkit with learning and memory capabilities."""
        # Initialize managers if not provided
        if self._persona_manager is None:
            self._persona_manager = PersonaManager(
                user_id=self._session_id or "default",
                emdash_dir=self._repo_root / ".emdash",
            )

        if self._capability_registry is None:
            self._capability_registry = CapabilityRegistry(
                emdash_dir=self._repo_root / ".emdash",
            )

        if self._hierarchical_memory is None:
            self._hierarchical_memory = HierarchicalMemory(
                session_id=self._session_id or "default",
                emdash_dir=self._repo_root / ".emdash",
            )

        if self._skill_memory_manager is None:
            self._skill_memory_manager = SkillMemoryManager(
                storage_dir=self._repo_root / ".emdash" / "memory" / "skills",
            )

        # Create memory tools wrapper
        self._memory_tools = MemoryToolSet(self._hierarchical_memory)

        # Create toolkit with all capabilities
        return OpenToolkit(
            repo_root=self._repo_root,
            persona_manager=self._persona_manager,
            capability_registry=self._capability_registry,
            hierarchical_memory=self._hierarchical_memory,
            skill_memory_manager=self._skill_memory_manager,
            memory_tools=self._memory_tools,
        )

    def _build_system_prompt(self) -> str:
        """Build a dynamic system prompt with all memory layers."""
        # Ensure toolkit is created
        if not hasattr(self, 'toolkit') or self.toolkit is None:
            return OPEN_BOOTSTRAP_PROMPT

        return build_open_system_prompt(
            persona_manager=self._persona_manager,
            capability_registry=self._capability_registry,
            hierarchical_memory=self._hierarchical_memory,
            skill_memory_manager=self._skill_memory_manager,
            toolkit=self.toolkit,
        )

    def _get_available_subagents(self) -> dict[str, str]:
        """Return available sub-agent types."""
        return {
            "explore": "Read-only exploration of codebase",
            "plan": "Deep analysis and architecture planning",
            "research": "Web research and information gathering",
            "coder": "Code implementation (when file changes needed)",
        }

    def _pre_run(self, query: str, **kwargs) -> None:
        """Hook called before running the agent.
        
        Adds memory-enhanced context:
        - Capability detection
        - Applicable rule/skill activation
        - Persona interaction tracking
        - Short-term memory logging
        """
        # Check for pending channel messages
        try:
            from ...channels.stored import StoredChannel
            stored = StoredChannel(self._repo_root / ".emdash")
            pending = stored.get_and_clear()
            if pending and self._hierarchical_memory:
                self._hierarchical_memory.short_term.add(
                    content=f"Pending notifications: {pending}",
                    context="channels",
                    importance=0.8,
                )
        except Exception:
            pass  # Channel system not available

        # Record this interaction in short-term memory
        if self._hierarchical_memory:
            self._hierarchical_memory.short_term.add(
                content=f"User query: {query[:200]}",  # Truncate for short-term
                context="session",
                importance=0.5,
            )

        # Track onboarding state in short-term memory
        if self._hierarchical_memory and self._persona_manager:
            core_count = len(self._hierarchical_memory.core.entries)
            interaction_count = self._persona_manager.interaction_count
            if core_count < 5 and interaction_count <= 10:
                self._hierarchical_memory.short_term.add(
                    content=f"Onboarding in progress. Core identity entries: {core_count}/5.",
                    context="onboarding",
                    importance=0.6,
                )

        # Record persona interaction
        if self._persona_manager:
            self._persona_manager.record_interaction()

        # Check for capability matches
        if self._capability_registry:
            matching_caps = self._capability_registry.find_matching(query)
            if matching_caps:
                self._capability_registry.record_usage(matching_caps[0].name)

        # Find applicable rules and skills
        active_rules = []
        active_skills = []

        if self._skill_memory_manager:
            active_rules = self._skill_memory_manager.find_applicable_rules(query)
            active_skills = self._skill_memory_manager.find_applicable_skills(query)

        # Add active rules/skills to short-term memory for this session
        if active_rules and self._hierarchical_memory:
            self._hierarchical_memory.short_term.add(
                content=f"Active rules: {[r.action for r in active_rules]}",
                context="rules",
                importance=0.7,
            )

        if active_skills and self._hierarchical_memory:
            self._hierarchical_memory.short_term.add(
                content=f"Active skills: {[s.name for s in active_skills]}",
                context="skills",
                importance=0.7,
            )

        # Rebuild system prompt with current context
        self.system_prompt = self._build_system_prompt()

    def run(self, query: str, **kwargs):
        """Run the agent with memory-enhanced context.
        
        Overrides parent to add pre-run memory setup.
        """
        # Set up memory context before running
        self._pre_run(query, **kwargs)
        
        # Run parent implementation synchronously
        return super().run(query, **kwargs)

    def _on_run_complete(self, response: str) -> None:
        """Hook called when a run completes successfully.
        
        Adds completion to short-term memory.
        """
        super()._on_run_complete(response)
        
        # After response, add to short-term memory
        if self._hierarchical_memory:
            self._hierarchical_memory.short_term.add(
                content="Completed processing query",
                context="session",
                importance=0.3,
            )

    # Memory Management Interface

    def remember(
        self,
        content: str,
        layer: str = "long_term",
        importance: float = 0.5,
        context: str = "general",
        tags: Optional[list] = None,
    ) -> dict:
        """Store something in memory.

        Args:
            content: Information to remember
            layer: Which layer ("core", "long_term", "short_term")
            importance: Importance score (0.0-1.0)
            context: Category
            tags: Optional tags

        Returns:
            Result dict
        """
        if self._memory_tools:
            return self._memory_tools.remember(
                content=content,
                layer=layer,
                importance=importance,
                context=context,
                tags=tags,
            )
        return {"success": False, "error": "Memory not initialized"}

    def recall(
        self,
        query: str,
        layers: Optional[list] = None,
        context: Optional[str] = None,
        top_k: int = 5,
    ) -> dict:
        """Search memory.

        Args:
            query: Search query
            layers: Which layers to search
            context: Filter by context
            top_k: Max results

        Returns:
            Matching memories
        """
        if self._memory_tools:
            return self._memory_tools.recall(
                query=query,
                layers=layers,
                context=context,
                top_k=top_k,
            )
        return {"success": False, "error": "Memory not initialized"}

    def show_memory_stats(self) -> dict:
        """Get memory statistics."""
        if self._memory_tools:
            return self._memory_tools.get_memory_stats()
        return {"success": False, "error": "Memory not initialized"}

    def export_memory(self) -> dict:
        """Export all memory layers."""
        if self._hierarchical_memory:
            return {
                "success": True,
                "memory": self._hierarchical_memory.export_all(),
                "skills_rules": self._skill_memory_manager.export_all() if self._skill_memory_manager else {},
            }
        return {"success": False, "error": "Memory not initialized"}

    # Persona Interface (backward compatible)

    def get_persona_summary(self) -> str:
        """Get a summary of the learned persona."""
        if self._persona_manager:
            return self._persona_manager.get_preference_summary()
        return "No persona data available."

    def export_persona(self, file_path: Optional[str] = None) -> dict:
        """Export the current persona."""
        if not self._persona_manager:
            return {}

        data = self._persona_manager.export_persona()

        if file_path:
            import json
            with open(file_path, "w") as f:
                json.dump(data, f, indent=2)

        return data

    def import_persona(self, data: dict) -> None:
        """Import persona data."""
        if self._persona_manager:
            self._persona_manager.import_persona(data)
            self.system_prompt = self._build_system_prompt()

    def reset_persona(self) -> None:
        """Reset the persona to initial state."""
        if self._persona_manager:
            self._persona_manager.reset()
        if self._capability_registry:
            self._capability_registry.reset()
        if self._hierarchical_memory:
            self._hierarchical_memory.reset()
        if self._skill_memory_manager:
            # Clear skills and rules
            for skill_id in list(self._skill_memory_manager.skills.keys()):
                del self._skill_memory_manager.skills[skill_id]
            self._skill_memory_manager._save_skills()
            for rule_id in list(self._skill_memory_manager.rules.keys()):
                del self._skill_memory_manager.rules[rule_id]
            self._skill_memory_manager._save_rules()

        self.system_prompt = self._build_system_prompt()

    # Capabilities Interface

    def list_capabilities(self) -> list:
        """List all learned capabilities."""
        caps = []
        if self._capability_registry:
            caps.extend([
                {
                    "name": c.name,
                    "description": c.description,
                    "usage_count": c.usage_count,
                    "enabled": c.enabled,
                    "type": "capability",
                }
                for c in self._capability_registry.list_all()
            ])
        if self._skill_memory_manager:
            caps.extend([
                {
                    "name": s.name,
                    "description": s.description,
                    "usage_count": s.use_count,
                    "enabled": s.enabled,
                    "type": "skill",
                }
                for s in self._skill_memory_manager.skills.values()
            ])
        return caps

    def list_rules(self) -> list:
        """List all active rules."""
        if self._skill_memory_manager:
            return [
                {
                    "id": r.id,
                    "trigger": r.trigger,
                    "action": r.action,
                    "scope": r.scope,
                    "priority": r.priority,
                    "enabled": r.enabled,
                    "usage_count": r.usage_count,
                }
                for r in self._skill_memory_manager.rules.values()
            ]
        return []

    def suggest_capability(self, message: str) -> Optional[dict]:
        """Suggest creating a new capability based on message."""
        if self._capability_registry:
            history = []
            return self._capability_registry.suggest_new(message, history)
        return None


# Re-export the bootstrap prompt
from .prompts import OPEN_BOOTSTRAP_PROMPT