"""
CrewAI Integration - Native adapter for CrewAI framework
"""

from datetime import datetime
from typing import Any, Callable, Dict, List, Optional, Union


class CrewAIEntropyAdapter:
    """
    Adapter for CrewAI that monitors and regulates entropy in crews.

    Usage:
        from crewai import Crew, Agent, Task
        from entropic_core.integrations import CrewAIEntropyAdapter

        crew = Crew(agents=[...], tasks=[...])
        adapter = CrewAIEntropyAdapter(crew, entropy_brain)
        result = adapter.kickoff()
    """

    def __init__(
        self, crew_or_brain: Any = None, entropy_brain: Any = None, crew: Any = None
    ):
        """
        Initialize the CrewAI adapter.
        Supports both Adapter(crew, brain) and Adapter(brain) signatures.
        """
        self.wrapped_agents = []
        self.task_entropy_history = []
        self.agent_interactions = []

        # Logic to handle flexible initialization (Standard vs Test/Mock)
        if entropy_brain is None and crew is None and hasattr(crew_or_brain, "measure"):
            # Initialized as Adapter(brain) -> Testing/Mocking mode
            self.entropy_brain = crew_or_brain
            self.crew = None
        else:
            # Initialized as Adapter(crew, brain) -> Standard mode
            self.crew = crew if crew else crew_or_brain
            self.entropy_brain = entropy_brain

        # If we have both immediately, wrap them
        if self.crew and self.entropy_brain:
            self._wrap_crew_agents()

    def _wrap_crew_agents(self):
        """Wraps crew agents with entropy awareness"""
        from entropic_core.core.agent_adapter import AgentAdapter

        # Check if crew has agents (it might be a mock object or real Crew)
        agents = getattr(self.crew, "agents", [])

        for agent in agents:
            role = getattr(agent, "role", "unknown")
            wrapped = AgentAdapter.wrap_agent(agent, agent_id=role)
            self.wrapped_agents.append(wrapped)

        if self.wrapped_agents and self.entropy_brain:
            self.entropy_brain.connect(self.wrapped_agents)

    def monitor_crew(self, agents_data: List[Union[Dict, Any]]) -> None:
        """
        Monitor a list of agents explicitly (used for tests or manual setup).

        Args:
            agents_data: List of agent dicts or objects
        """
        from entropic_core.core.agent_adapter import AgentAdapter

        for agent_data in agents_data:
            name = "unknown"
            if isinstance(agent_data, dict):
                name = agent_data.get("name", "unknown")
            elif hasattr(agent_data, "role"):
                name = agent_data.role

            # Create a mock agent adapter for monitoring
            wrapped = AgentAdapter.create_mock_agent(agent_id=name, behavior="balanced")
            self.wrapped_agents.append(wrapped)

        if self.entropy_brain:
            self.entropy_brain.connect(self.wrapped_agents)

    def kickoff(self, inputs: Dict = None) -> Any:
        """
        Executes the crew with entropy monitoring.

        Args:
            inputs: Optional inputs for the crew

        Returns:
            Result from the crew execution
        """
        if not self.crew:
            raise ValueError("No Crew instance provided to kickoff")

        if self.entropy_brain:
            # Measure initial entropy
            initial_entropy = self.entropy_brain.measure()
            self._log_entropy_event("crew_start", initial_entropy)

        # Execute crew tasks
        try:
            result = self.crew.kickoff(inputs=inputs)

            if self.entropy_brain:
                # Measure final entropy
                final_entropy = self.entropy_brain.measure()
                self._log_entropy_event("crew_complete", final_entropy)

                # Regulate if needed
                if final_entropy["combined"] > 0.7 or final_entropy["combined"] < 0.3:
                    action = self.entropy_brain.regulate()
                    self._log_entropy_event("regulation", final_entropy, action)

            return result

        except Exception as e:
            if self.entropy_brain:
                error_entropy = self.entropy_brain.measure()
                self._log_entropy_event("crew_error", error_entropy, str(e))
            raise

    def _log_entropy_event(self, event_type: str, entropy: Dict, details: Any = None):
        """Logs entropy events during crew execution"""
        agent_count = (
            len(self.crew.agents)
            if self.crew and hasattr(self.crew, "agents")
            else len(self.wrapped_agents)
        )

        event = {
            "timestamp": datetime.now().isoformat(),
            "event_type": event_type,
            "entropy": entropy,
            "details": details,
            "agent_count": agent_count,
        }
        self.task_entropy_history.append(event)

        if self.entropy_brain and hasattr(self.entropy_brain, "memory"):
            self.entropy_brain.memory.log_event(
                entropy=entropy.get("combined", 0.5),
                event_type=event_type,
                action=str(details) if details else None,
                metadata={"crew_size": agent_count},
            )

    def get_entropy_report(self) -> Dict:
        """
        Generates entropy report for the crew execution.

        Returns:
            Dictionary with entropy statistics and events
        """
        if not self.task_entropy_history:
            return {"status": "no_data", "events": []}

        entropies = [
            e["entropy"]["combined"]
            for e in self.task_entropy_history
            if "entropy" in e
        ]

        agent_count = (
            len(self.crew.agents)
            if self.crew and hasattr(self.crew, "agents")
            else len(self.wrapped_agents)
        )

        return {
            "total_events": len(self.task_entropy_history),
            "avg_entropy": sum(entropies) / len(entropies) if entropies else 0,
            "max_entropy": max(entropies) if entropies else 0,
            "min_entropy": min(entropies) if entropies else 0,
            "events": self.task_entropy_history,
            "agent_count": agent_count,
        }

    def monitor_task_execution(self, task: Any, callback: Optional[Callable] = None):
        """
        Monitors entropy during individual task execution.

        Args:
            task: CrewAI Task instance
            callback: Optional callback when entropy exceeds thresholds
        """
        if not self.entropy_brain:
            raise ValueError("EntropyBrain required for task monitoring")

        # Measure before task
        before_entropy = self.entropy_brain.measure()

        # Execute task
        result = task.execute()

        # Measure after task
        after_entropy = self.entropy_brain.measure()

        # Calculate delta
        entropy_delta = after_entropy["combined"] - before_entropy["combined"]

        self._log_entropy_event(
            "task_executed",
            after_entropy,
            {"task_description": task.description, "entropy_delta": entropy_delta},
        )

        # Trigger callback if provided
        if callback and abs(entropy_delta) > 0.2:
            callback(task, before_entropy, after_entropy, entropy_delta)

        return result


class CrewAIEntropyCallback:
    """
    Callback handler for CrewAI that automatically monitors entropy.

    Usage:
        callback = CrewAIEntropyCallback(entropy_brain)
        crew = Crew(agents=[...], tasks=[...], callbacks=[callback])
    """

    def __init__(self, entropy_brain: Any):
        self.entropy_brain = entropy_brain
        self.events = []

    def on_task_start(self, task: Any):
        """Called when a task starts"""
        entropy = self.entropy_brain.measure()
        self.events.append(
            {
                "type": "task_start",
                "task": task.description if hasattr(task, "description") else str(task),
                "entropy": entropy,
                "timestamp": datetime.now().isoformat(),
            }
        )

    def on_task_end(self, task: Any, output: Any):
        """Called when a task ends"""
        entropy = self.entropy_brain.measure()
        self.events.append(
            {
                "type": "task_end",
                "task": task.description if hasattr(task, "description") else str(task),
                "entropy": entropy,
                "output": str(output)[:100],  # First 100 chars
                "timestamp": datetime.now().isoformat(),
            }
        )

        # Auto-regulate if needed
        if entropy["combined"] > 0.75:
            action = self.entropy_brain.regulate()
            self.events.append(
                {
                    "type": "auto_regulation",
                    "action": action,
                    "timestamp": datetime.now().isoformat(),
                }
            )

    def get_summary(self) -> Dict:
        """Returns summary of entropy events"""
        return {"total_events": len(self.events), "events": self.events}


CrewAIEntropyMonitor = CrewAIEntropyAdapter  # Alias for backward compatibility

__all__ = ["CrewAIEntropyAdapter", "CrewAIEntropyCallback", "CrewAIEntropyMonitor"]
