"""Skill-based memory and rules system.

Integrates skills with the hierarchical memory to create dynamic, learned behaviors.
"""

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


@dataclass
class MemoryRule:
    """A rule that triggers based on memory patterns.

    Example:
        trigger: "user mentions preferring short responses"
        action: "use concise communication style"
        scope: "all_conversations"
    """

    id: str
    trigger: str  # Natural language description of when this applies
    condition: str  # Simple pattern or expression to match
    action: str  # What to do when triggered
    scope: str = "all"  # "all", "coding", "planning", etc.
    priority: int = 1  # Higher = more important
    source: str = "learned"  # "user_defined", "learned", "system"
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    usage_count: int = 0
    last_triggered: Optional[str] = None
    enabled: bool = True

    def to_dict(self) -> dict:
        return asdict(self)

    @classmethod
    def from_dict(cls, data: dict) -> "MemoryRule":
        return cls(**data)

    def matches(self, text: str, context: Optional[dict] = None) -> bool:
        """Check if this rule matches the given text/context."""
        # Simple keyword matching for now
        # Could be enhanced with embeddings or more sophisticated matching
        pattern = self.condition.lower()
        text_lower = text.lower()

        # Check for direct keyword matches
        keywords = [w.strip() for w in pattern.split() if len(w.strip()) > 3]
        matches = sum(1 for kw in keywords if kw in text_lower)

        return matches >= max(1, len(keywords) // 2)  # At least half the keywords


@dataclass
class LearnedSkill:
    """A skill learned from interactions.

    Unlike hardcoded skills, these are discovered through usage patterns
    and stored in long-term memory.
    """

    id: str
    name: str
    description: str
    trigger_patterns: List[str]  # When to activate this skill
    prompt_addition: str  # What to add to system prompt
    examples: List[str]  # Example usage
    source_interactions: List[str]  # IDs of interactions that created this
    importance: float = 0.5
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    last_used: Optional[str] = None
    use_count: int = 0
    enabled: bool = True

    def to_dict(self) -> dict:
        return asdict(self)

    @classmethod
    def from_dict(cls, data: dict) -> "LearnedSkill":
        return cls(**data)

    def should_activate(self, query: str, context: Optional[dict] = None) -> bool:
        """Determine if this skill should be activated."""
        query_lower = query.lower()

        for pattern in self.trigger_patterns:
            if pattern.lower() in query_lower:
                return True

        return False

    def touch(self):
        """Update usage stats."""
        self.last_used = datetime.now().isoformat()
        self.use_count += 1


class SkillMemoryManager:
    """Manages learned skills and memory-based rules."""

    MAX_RULES = 50
    MAX_SKILLS = 30

    def __init__(self, storage_dir: Optional[Path] = None):
        self._storage_dir = storage_dir or Path(".emdash/memory/skills")
        self._storage_dir.mkdir(parents=True, exist_ok=True)

        self._rules_path = self._storage_dir / "rules.json"
        self._skills_path = self._storage_dir / "skills.json"

        self.rules: Dict[str, MemoryRule] = {}
        self.skills: Dict[str, LearnedSkill] = {}

        self._load()

    def _load(self) -> None:
        """Load rules and skills from storage."""
        if self._rules_path.exists():
            try:
                with open(self._rules_path, "r") as f:
                    data = json.load(f)
                self.rules = {
                    k: MemoryRule.from_dict(v) for k, v in data.get("rules", {}).items()
                }
            except Exception as e:
                print(f"Warning: Could not load rules: {e}")

        if self._skills_path.exists():
            try:
                with open(self._skills_path, "r") as f:
                    data = json.load(f)
                self.skills = {
                    k: LearnedSkill.from_dict(v) for k, v in data.get("skills", {}).items()
                }
            except Exception as e:
                print(f"Warning: Could not load skills: {e}")

    def _save_rules(self) -> None:
        """Save rules to storage."""
        data = {
            "updated_at": datetime.now().isoformat(),
            "rule_count": len(self.rules),
            "rules": {k: v.to_dict() for k, v in self.rules.items()},
        }
        with open(self._rules_path, "w") as f:
            json.dump(data, f, indent=2)

    def _save_skills(self) -> None:
        """Save skills to storage."""
        data = {
            "updated_at": datetime.now().isoformat(),
            "skill_count": len(self.skills),
            "skills": {k: v.to_dict() for k, v in self.skills.items()},
        }
        with open(self._skills_path, "w") as f:
            json.dump(data, f, indent=2)

    # Rule Management

    def add_rule(
        self,
        trigger: str,
        condition: str,
        action: str,
        scope: str = "all",
        priority: int = 1,
        rule_id: Optional[str] = None,
    ) -> str:
        """Add a new memory rule.

        Args:
            trigger: Natural language description of when to apply
            condition: Pattern to match against
            action: What to do when triggered
            scope: Which contexts this applies to
            priority: Importance level
            rule_id: Optional specific ID

        Returns:
            The rule ID
        """
        if len(self.rules) >= self.MAX_RULES:
            # Remove lowest priority, least used rule
            candidates = sorted(
                self.rules.items(),
                key=lambda x: (x[1].priority, x[1].usage_count)
            )
            del self.rules[candidates[0][0]]

        rule_id = rule_id or f"rule_{datetime.now().strftime('%Y%m%d_%H%M%S')}"

        self.rules[rule_id] = MemoryRule(
            id=rule_id,
            trigger=trigger,
            condition=condition,
            action=action,
            scope=scope,
            priority=priority,
        )

        self._save_rules()
        return rule_id

    def find_applicable_rules(self, text: str, scope: str = "all") -> List[MemoryRule]:
        """Find rules that match the given text."""
        applicable = []

        for rule in self.rules.values():
            if not rule.enabled:
                continue
            if scope != "all" and rule.scope not in ["all", scope]:
                continue
            if rule.matches(text):
                applicable.append(rule)
                rule.usage_count += 1
                rule.last_triggered = datetime.now().isoformat()

        # Sort by priority
        applicable.sort(key=lambda r: -r.priority)

        if applicable:
            self._save_rules()

        return applicable

    def apply_rules_to_prompt(self, rules: List[MemoryRule]) -> str:
        """Generate prompt additions from rules."""
        if not rules:
            return ""

        lines = ["## Active Rules"]
        for rule in rules:
            lines.append(f"- {rule.action}")

        return "\n".join(lines)

    # Skill Management

    def learn_skill(
        self,
        name: str,
        description: str,
        trigger_patterns: List[str],
        prompt_addition: str,
        examples: Optional[List[str]] = None,
        source_interaction: Optional[str] = None,
        importance: float = 0.5,
    ) -> str:
        """Learn a new skill from an interaction.

        Args:
            name: Skill name
            description: What this skill does
            trigger_patterns: Keywords/phrases that trigger this skill
            prompt_addition: System prompt addition when active
            examples: Usage examples
            source_interaction: ID of interaction that created this
            importance: How important/versatile this skill is

        Returns:
            The skill ID
        """
        if len(self.skills) >= self.MAX_SKILLS:
            # Remove least used skill
            candidates = sorted(
                self.skills.items(),
                key=lambda x: (x[1].importance, x[1].use_count)
            )
            del self.skills[candidates[0][0]]

        skill_id = f"skill_{name.lower().replace(' ', '_')}"

        # Handle duplicates
        base_id = skill_id
        counter = 1
        while skill_id in self.skills:
            skill_id = f"{base_id}_{counter}"
            counter += 1

        sources = [source_interaction] if source_interaction else []

        self.skills[skill_id] = LearnedSkill(
            id=skill_id,
            name=name,
            description=description,
            trigger_patterns=trigger_patterns,
            prompt_addition=prompt_addition,
            examples=examples or [],
            source_interactions=sources,
            importance=importance,
        )

        self._save_skills()
        return skill_id

    def find_applicable_skills(self, query: str, top_k: int = 3) -> List[LearnedSkill]:
        """Find skills that should be activated for this query."""
        matches = []

        for skill in self.skills.values():
            if not skill.enabled:
                continue
            if skill.should_activate(query):
                matches.append(skill)
                skill.touch()

        # Sort by importance and recency
        matches.sort(key=lambda s: (-s.importance, s.use_count))

        if matches:
            self._save_skills()

        return matches[:top_k]

    def build_skills_prompt(self, skills: List[LearnedSkill]) -> str:
        """Build prompt section from active skills."""
        if not skills:
            return ""

        sections = ["## Active Skills"]

        for skill in skills:
            sections.append(f"\n### {skill.name}")
            sections.append(f"*{skill.description}*")
            sections.append(skill.prompt_addition)

            if skill.examples:
                sections.append("\nExamples:")
                for ex in skill.examples[:2]:  # Limit examples
                    sections.append(f"- {ex}")

        return "\n".join(sections)

    def get_skill_suggestions(self, recent_interactions: List[str]) -> List[dict]:
        """Analyze recent interactions and suggest new skills to learn."""
        suggestions = []

        # Look for patterns in recent interactions that could become skills
        # This is a simple implementation - could be enhanced with LLM analysis

        # Check for repeated requests with similar structure
        if len(recent_interactions) >= 3:
            # Simple heuristic: if similar phrasing appears multiple times
            # it might be a pattern worth learning
            common_prefix = self._find_common_prefix(recent_interactions)

            if common_prefix and len(common_prefix) > 20:
                suggestions.append({
                    "type": "pattern",
                    "suggested_name": "Custom Pattern Handler",
                    "trigger": common_prefix,
                    "reason": "Repeated pattern detected",
                    "action": "Learn a skill to handle this pattern efficiently",
                })

        # Check for explicit teaching moments
        teaching_patterns = [
            "always do",
            "from now on",
            "remember to",
            "whenever I",
            "every time",
        ]

        for interaction in recent_interactions:
            lower = interaction.lower()
            for pattern in teaching_patterns:
                if pattern in lower:
                    suggestions.append({
                        "type": "teaching",
                        "source": interaction,
                        "trigger": pattern,
                        "reason": "User teaching pattern detected",
                        "action": "Consider learning this as a rule or skill",
                    })
                    break

        return suggestions

    def _find_common_prefix(self, strings: List[str]) -> str:
        """Find the longest common prefix among strings."""
        if not strings:
            return ""

        prefix = strings[0]
        for s in strings[1:]:
            while not s.startswith(prefix):
                prefix = prefix[:-1]
                if not prefix:
                    return ""

        return prefix

    def export_all(self) -> dict:
        """Export all rules and skills."""
        return {
            "rules": {
                "count": len(self.rules),
                "items": {k: v.to_dict() for k, v in self.rules.items()},
            },
            "skills": {
                "count": len(self.skills),
                "items": {k: v.to_dict() for k, v in self.skills.items()},
            },
        }

    def get_stats(self) -> dict:
        """Get statistics about rules and skills."""
        return {
            "rules": {
                "total": len(self.rules),
                "max": self.MAX_RULES,
                "enabled": sum(1 for r in self.rules.values() if r.enabled),
                "by_scope": self._count_by_scope(),
            },
            "skills": {
                "total": len(self.skills),
                "max": self.MAX_SKILLS,
                "enabled": sum(1 for s in self.skills.values() if s.enabled),
                "avg_importance": sum(s.importance for s in self.skills.values()) / max(1, len(self.skills)),
            },
        }

    def _count_by_scope(self) -> dict:
        """Count rules by scope."""
        counts = {}
        for rule in self.rules.values():
            scope = rule.scope
            counts[scope] = counts.get(scope, 0) + 1
        return counts