"""Agent wrapper for automatic learning.

AUTOMATIC LEARNING:
This wrapper intercepts agent method calls and automatically:
1. Learns from successful operations (patterns, insights)
2. Captures failures as gotchas
3. Detects preferences from text outputs
4. Tracks surprise moments (expected vs actual)

Integration with SparkNet Intelligence modules:
- ContentLearner: Analyzes code outputs for patterns
- SemanticIntentDetector: Captures preferences from text
- AhaTracker: Records surprise moments
"""

from typing import Any, Callable, Optional, Dict, List
import functools
import inspect
import time
from datetime import datetime

from .client import SparkNet
from .models import SparkType, KnowledgeType

# Try to import intelligence modules (optional - graceful degradation)
try:
    import sys
    from pathlib import Path
    # Add packages path for intelligence modules
    packages_path = Path(__file__).parent.parent / "packages" / "sparknet"
    if packages_path.exists():
        sys.path.insert(0, str(packages_path.parent.parent))
    from packages.sparknet.intelligence.content_learner import ContentLearner
    from packages.sparknet.intelligence.semantic_intent import SemanticIntentDetector
    INTELLIGENCE_AVAILABLE = True
except ImportError:
    INTELLIGENCE_AVAILABLE = False
    ContentLearner = None
    SemanticIntentDetector = None


class SparkWrapper:
    """
    Wrapper that adds learning capabilities to any agent.

    Intercepts method calls to capture learning opportunities:
    - Success patterns → Pattern sparks
    - Failures → Gotcha sparks
    - Text preferences → Preference sparks
    - Surprises → Eureka sparks
    """

    def __init__(self, agent: Any, sparknet: SparkNet):
        self._agent = agent
        self._sparknet = sparknet
        self._context: Dict[str, Any] = {}
        self._call_history: List[Dict] = []
        self._success_count: Dict[str, int] = {}
        self._failure_count: Dict[str, int] = {}

        # Initialize intelligence modules if available
        self._content_learner = ContentLearner() if INTELLIGENCE_AVAILABLE and ContentLearner else None
        self._intent_detector = SemanticIntentDetector() if INTELLIGENCE_AVAILABLE and SemanticIntentDetector else None

        # Track domain from agent class name
        self._domain = self._infer_domain(agent)

    def _infer_domain(self, agent: Any) -> Optional[str]:
        """Infer domain from agent class name."""
        class_name = type(agent).__name__.lower()
        # Common domain indicators
        domain_hints = {
            "code": "coding",
            "chat": "conversation",
            "search": "search",
            "data": "data",
            "file": "filesystem",
            "web": "web",
            "api": "api",
            "test": "testing",
            "doc": "documentation",
        }
        for hint, domain in domain_hints.items():
            if hint in class_name:
                return domain
        return None

    def __getattr__(self, name: str):
        attr = getattr(self._agent, name)
        if callable(attr):
            return self._wrap_method(attr, name)
        return attr

    def _wrap_method(self, method: Callable, method_name: str) -> Callable:
        @functools.wraps(method)
        def wrapper(*args, **kwargs):
            start_time = time.time()

            # Execute the method
            try:
                result = method(*args, **kwargs)
                duration = time.time() - start_time

                # Capture success patterns (if meaningful)
                self._capture_success(method_name, args, kwargs, result, duration)

                return result
            except Exception as e:
                duration = time.time() - start_time
                # Capture failure as gotcha
                self._capture_failure(method_name, args, kwargs, e, duration)
                raise

        return wrapper

    def _capture_success(self, method: str, args, kwargs, result, duration: float):
        """
        Capture learning from successful operations.

        Analyzes:
        - Method success patterns (which methods work reliably)
        - Code outputs (via ContentLearner)
        - Text outputs for preferences (via SemanticIntentDetector)
        - Surprising results (expected vs actual)
        """
        # Skip private methods
        if method.startswith("_"):
            return

        # Track success count for this method
        self._success_count[method] = self._success_count.get(method, 0) + 1
        count = self._success_count[method]

        # Record call in history
        self._call_history.append({
            "method": method,
            "success": True,
            "duration": duration,
            "timestamp": datetime.now().isoformat(),
        })

        # Update context
        self._context["last_method"] = method
        self._context["last_success"] = True
        self._context["last_result_type"] = type(result).__name__

        # === ACTUAL LEARNING ===

        # 1. Learn pattern: method is reliable (after 3+ successes with no failures)
        if count >= 3 and self._failure_count.get(method, 0) == 0:
            if count == 3:  # Only learn once at threshold
                self._learn_reliable_method(method)

        # 2. Analyze code outputs with ContentLearner
        if self._content_learner and self._is_code_output(result):
            self._learn_from_code_result(method, result)

        # 3. Detect preferences in text outputs
        if self._intent_detector and isinstance(result, str) and len(result) > 20:
            self._learn_from_text_result(method, result)

        # 4. Check for surprising results (method returned unexpected type/value)
        self._check_for_surprise(method, result)

    def _learn_reliable_method(self, method: str):
        """Learn that a method is reliable."""
        try:
            agent_name = type(self._agent).__name__
            self._sparknet.learn(
                content=f"{agent_name}.{method}() is reliable - succeeded {self._success_count[method]} times without failure",
                spark_type=SparkType.PATTERN,
                knowledge_type=KnowledgeType.PROCEDURAL,
                domain=self._domain,
                confidence=0.8,
                how_learned="automatic observation",
                tags=["reliability", "method-pattern", agent_name.lower()],
            )
        except Exception:
            pass

    def _learn_from_code_result(self, method: str, result: Any):
        """Analyze code output for patterns using ContentLearner."""
        try:
            code = str(result) if not isinstance(result, str) else result

            # Only analyze if it looks like code
            if not any(indicator in code for indicator in ["def ", "class ", "import ", "function ", "const ", "=>"]):
                return

            # Use ContentLearner to extract patterns
            hits = self._content_learner.learn_from_code(code, f"{method}_output")

            # Create sparks for promoted patterns
            for hit in hits:
                if hit.promote:
                    lang_note = f" in {hit.language}" if hit.language else ""
                    self._sparknet.learn(
                        content=f"Pattern detected{lang_note}: {hit.value.replace('_', ' ')} ({hit.pattern_type})",
                        spark_type=SparkType.PATTERN,
                        knowledge_type=KnowledgeType.PROCEDURAL,
                        domain=hit.language or self._domain,
                        confidence=0.7,
                        how_learned=f"content analysis from {method}()",
                        tags=["code-pattern", hit.pattern_type],
                    )
        except Exception:
            pass

    def _learn_from_text_result(self, method: str, text: str):
        """Detect preferences in text output."""
        try:
            signal = self._intent_detector.detect(text, session_id=f"wrapper_{id(self)}")
            if signal and signal.confidence >= 0.7 and signal.wanted:
                # Create preference spark
                content = f"Preference: {signal.wanted}"
                if signal.rejected:
                    content = f"Prefers {signal.wanted} over {signal.rejected}"

                self._sparknet.preference(
                    what=signal.wanted,
                    why=f"Detected from {method}() output",
                    domain=self._domain,
                )
        except Exception:
            pass

    def _check_for_surprise(self, method: str, result: Any):
        """Check if result is surprising compared to history."""
        # Get expected result type from history
        method_history = [h for h in self._call_history[-20:] if h["method"] == method and h["success"]]
        if len(method_history) < 3:
            return  # Not enough history

        # Check if result type changed
        result_type = type(result).__name__
        expected_type = self._context.get(f"expected_type_{method}")

        if expected_type and expected_type != result_type:
            # Surprise! Result type changed
            try:
                self._sparknet.aha(
                    expected=f"{method}() returns {expected_type}",
                    actual=f"{method}() returned {result_type}",
                    lesson=f"Method {method} can return different types depending on context",
                    domain=self._domain,
                )
            except Exception:
                pass

        # Update expected type
        self._context[f"expected_type_{method}"] = result_type

    def _is_code_output(self, result: Any) -> bool:
        """Check if result looks like code."""
        if not isinstance(result, str):
            return False
        code_indicators = ["def ", "class ", "import ", "function ", "const ", "let ", "var ", "=>", "return "]
        return any(ind in result for ind in code_indicators)

    def _capture_failure(self, method: str, args, kwargs, error: Exception, duration: float):
        """Capture learning from failures as gotchas."""
        # Skip if error is too generic
        if isinstance(error, (KeyboardInterrupt, SystemExit)):
            return

        # Track failure count
        self._failure_count[method] = self._failure_count.get(method, 0) + 1

        # Record in history
        self._call_history.append({
            "method": method,
            "success": False,
            "error": type(error).__name__,
            "duration": duration,
            "timestamp": datetime.now().isoformat(),
        })

        error_type = type(error).__name__
        error_msg = str(error)[:100]  # Truncate long messages

        # Record as gotcha if it seems meaningful
        if len(error_msg) > 10:
            try:
                agent_name = type(self._agent).__name__
                self._sparknet.gotcha(
                    pitfall=f"{agent_name}.{method}() can fail with {error_type}: {error_msg}",
                    solution=None,
                    domain=self._domain,
                )
            except Exception:
                pass  # Don't fail the original operation

    # === Public API ===

    def get_learning_stats(self) -> Dict[str, Any]:
        """Get statistics about what the wrapper has learned."""
        return {
            "total_calls": len(self._call_history),
            "success_count": sum(self._success_count.values()),
            "failure_count": sum(self._failure_count.values()),
            "methods_tracked": list(self._success_count.keys()),
            "reliable_methods": [m for m, c in self._success_count.items()
                                if c >= 3 and self._failure_count.get(m, 0) == 0],
            "unreliable_methods": [m for m, c in self._failure_count.items() if c >= 2],
            "domain": self._domain,
            "intelligence_available": INTELLIGENCE_AVAILABLE,
        }


def spark(agent: Any, **kwargs) -> SparkWrapper:
    """
    Wrap any agent to enable SparkNet learning.

    One line to make your agent smarter:

        from sparknet_sdk import spark
        agent = spark(my_agent)  # Now it actually learns!

    What the wrapper learns automatically:
    - Reliable methods (3+ successes without failure)
    - Code patterns from outputs (via ContentLearner)
    - Preferences from text outputs (via SemanticIntentDetector)
    - Surprise moments (unexpected result types)
    - Failures as gotchas

    Args:
        agent: The agent to wrap.
        **kwargs: Arguments passed to SparkNet.

    Returns:
        Wrapped agent with learning capabilities.

    Example:
        agent = spark(MyAgent())
        result = agent.process("Build a REST API")

        # Check what the wrapper learned
        stats = agent.get_learning_stats()
        print(f"Reliable methods: {stats['reliable_methods']}")
    """
    sparknet = SparkNet(**kwargs)
    return SparkWrapper(agent, sparknet)


def learn_from(func: Callable) -> Callable:
    """
    Decorator to capture learning from a function.

    Learns:
    - Successful patterns after 3+ calls
    - Failures as gotchas

    Usage:
        @learn_from
        def my_function():
            ...

        # Call it multiple times to learn patterns
        my_function()  # Call 1
        my_function()  # Call 2
        my_function()  # Call 3 -> learns "my_function is reliable"
    """
    success_count = [0]  # Use list for mutable closure
    failure_count = [0]

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        sparknet = SparkNet()
        start_time = time.time()

        try:
            result = func(*args, **kwargs)
            success_count[0] += 1

            # Learn reliability pattern after 3 successes
            if success_count[0] == 3 and failure_count[0] == 0:
                sparknet.learn(
                    content=f"Function {func.__name__}() is reliable - succeeded {success_count[0]} times",
                    spark_type=SparkType.PATTERN,
                    knowledge_type=KnowledgeType.PROCEDURAL,
                    confidence=0.8,
                    how_learned="automatic observation",
                    tags=["reliability", "function-pattern"],
                )

            return result
        except Exception as e:
            failure_count[0] += 1
            sparknet.gotcha(
                pitfall=f"{func.__name__}() failed with {type(e).__name__}: {str(e)[:80]}",
                solution=None,
            )
            raise

    return wrapper


# Export availability flag
__all__ = ["SparkWrapper", "spark", "learn_from", "INTELLIGENCE_AVAILABLE"]
