"""
Agent SDK Executor - Native Claude Agent SDK integration for Sugar 3.0

This executor replaces the subprocess-based ClaudeWrapper with direct
Claude Agent SDK integration, providing:
- Native Python SDK execution
- Hook-based quality gates
- MCP server support
- Observable execution
- Dynamic model routing by task complexity (AUTO-001)
- Real-time thinking capture for visibility into Claude's reasoning
"""

import os
import asyncio
import logging
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from .base import BaseExecutor, ExecutionResult
from ..agent import SugarAgent, SugarAgentConfig
from ..storage import IssueResponseManager
from ..profiles import IssueResponderProfile
from ..config import IssueResponderConfig
from ..integrations import GitHubClient
from ..ralph import RalphWiggumProfile, RalphConfig
from ..ralph.signals import (
    CompletionSignal,
    CompletionSignalDetector,
    CompletionType,
)
from ..orchestration.model_router import ModelRouter, ModelSelection
from .thinking_display import ThinkingCapture
from .hooks import HookExecutor

logger = logging.getLogger(__name__)


class AgentSDKExecutor(BaseExecutor):
    """
    Executor implementation using the Claude Agent SDK.

    This is Sugar 3.0's primary executor, providing native SDK-based
    task execution with full control over agent behavior.
    """

    def __init__(self, config: Dict[str, Any]):
        """
        Initialize the Agent SDK executor.

        Args:
            config: Configuration dictionary containing:
                - model: Claude model to use (default, can be overridden by routing)
                - models: Model tier configuration (simple, standard, complex)
                - timeout: Execution timeout in seconds
                - permission_mode: SDK permission mode
                - quality_gates: Quality gates configuration
                - mcp_servers: MCP server configurations
                - dry_run: Whether to simulate execution
        """
        super().__init__(config)

        # Agent configuration
        self.default_model = config.get("model", "claude-sonnet-4-20250514")
        self.model = self.default_model  # Current model (may be overridden per task)
        self.timeout = config.get("timeout", 300)
        self.permission_mode = config.get("permission_mode", "acceptEdits")

        # Model routing (AUTO-001)
        models_config = config.get("models", {})
        self.dynamic_routing_enabled = models_config.get("dynamic_routing", True)
        self._model_router = ModelRouter(config)

        # Quality gates
        self.quality_gates_config = config.get("quality_gates", {})
        self.quality_gates_enabled = self.quality_gates_config.get("enabled", True)

        # MCP servers
        self.mcp_servers = config.get("mcp_servers", {})

        # Log MCP server configuration for debugging
        if self.mcp_servers:
            logger.info(f"MCP servers configured: {list(self.mcp_servers.keys())}")
            for name, server_config in self.mcp_servers.items():
                logger.debug(f"  {name}: {server_config.get('command', 'N/A')}")
        else:
            logger.debug("No MCP servers configured")

        # Agent instance (lazy initialization)
        self._agent: Optional[SugarAgent] = None
        self._session_active = False
        self._current_model: Optional[str] = None  # Track model for current session

        # Completion signal detector for all executions
        self._signal_detector = CompletionSignalDetector()

        # Thinking capture
        self.thinking_capture_enabled = config.get("thinking_capture", True)

        logger.debug(
            f"AgentSDKExecutor initialized with default model: {self.default_model}"
        )
        logger.debug(f"Dynamic model routing enabled: {self.dynamic_routing_enabled}")
        logger.debug(f"Quality gates enabled: {self.quality_gates_enabled}")
        logger.debug(f"Thinking capture enabled: {self.thinking_capture_enabled}")
        # Hook executor for pre/post task hooks
        project_dir = config.get("project_dir", os.getcwd())
        self._hook_executor = HookExecutor(project_dir)
        self.hooks_enabled = config.get("hooks_enabled", True)
        logger.debug(f"Dry run mode: {self.dry_run}")

    def _create_agent_config(
        self,
        model: Optional[str] = None,
        allowed_tools: Optional[List[str]] = None,
        disallowed_tools: Optional[List[str]] = None,
    ) -> SugarAgentConfig:
        """Create agent configuration from executor config"""
        return SugarAgentConfig(
            model=model or self.model,
            permission_mode=self.permission_mode,
            mcp_servers=self.mcp_servers,
            quality_gates_enabled=self.quality_gates_enabled,
            timeout=self.timeout,
            allowed_tools=allowed_tools or self.config.get("allowed_tools", []),
            disallowed_tools=disallowed_tools
            or self.config.get("disallowed_tools", []),
        )

    def _get_tool_restrictions(
        self, task_type_info: Optional[Dict[str, Any]] = None
    ) -> Dict[str, Optional[List[str]]]:
        """
        Extract tool restrictions from task type information.

        Args:
            task_type_info: Optional task type information from database

        Returns:
            Dictionary with 'allowed_tools' and 'disallowed_tools' keys
        """
        if not task_type_info:
            return {"allowed_tools": None, "disallowed_tools": None}

        return {
            "allowed_tools": task_type_info.get("allowed_tools"),
            "disallowed_tools": task_type_info.get("disallowed_tools"),
        }

    def select_model_for_task(
        self,
        work_item: Dict[str, Any],
        task_type_info: Optional[Dict[str, Any]] = None,
    ) -> ModelSelection:
        """
        Select the appropriate model for a task using the ModelRouter.

        Args:
            work_item: Work item dictionary
            task_type_info: Optional task type information from database

        Returns:
            ModelSelection with chosen model and reasoning
        """
        if not self.dynamic_routing_enabled:
            # Return default model when routing is disabled
            from ..orchestration.model_router import ModelTier

            return ModelSelection(
                model=self.default_model,
                tier=ModelTier.STANDARD,
                reason="Dynamic routing disabled, using default model",
                task_type=work_item.get("type"),
                complexity_level=3,
                override_applied=False,
            )

        return self._model_router.route(work_item, task_type_info)

    async def _get_agent(
        self,
        model: Optional[str] = None,
        tool_restrictions: Optional[Dict[str, Optional[List[str]]]] = None,
        bash_permissions: Optional[List[str]] = None,
    ) -> SugarAgent:
        """Get or create the agent instance, optionally with a specific model, tool restrictions, and bash permissions"""
        target_model = model or self.model

        # If we need a different model than current, recreate the agent
        if self._agent is not None and self._current_model != target_model:
            if self._session_active:
                await self._agent.end_session()
                self._session_active = False
            self._agent = None

        # If tool restrictions or bash permissions are specified, always create a new agent
        # (these are per-task, not per-session)
        needs_custom_agent = (
            tool_restrictions
            and (
                tool_restrictions.get("allowed_tools")
                or tool_restrictions.get("disallowed_tools")
            )
        ) or bash_permissions is not None

        if needs_custom_agent:
            if self._agent is not None and self._session_active:
                await self._agent.end_session()
                self._session_active = False

            agent_config = self._create_agent_config(
                model=target_model,
                allowed_tools=(
                    tool_restrictions.get("allowed_tools")
                    if tool_restrictions
                    else None
                ),
                disallowed_tools=(
                    tool_restrictions.get("disallowed_tools")
                    if tool_restrictions
                    else None
                ),
            )
            # Create a new agent with tool restrictions and bash permissions
            return SugarAgent(
                config=agent_config,
                quality_gates_config=self.quality_gates_config,
                bash_permissions=bash_permissions,
            )

        if self._agent is None:
            agent_config = self._create_agent_config(model=target_model)
            self._agent = SugarAgent(
                config=agent_config,
                quality_gates_config=self.quality_gates_config,
            )
            self._current_model = target_model
            logger.debug(f"Created new agent instance with model: {target_model}")

        return self._agent

    def detect_completion_signal(self, content: str) -> CompletionSignal:
        """
        Detect completion signal in agent output.

        This method detects various completion signal patterns:
        - <promise>TEXT</promise>
        - <complete>TEXT</complete>
        - <done>TEXT</done>
        - TASK_COMPLETE: description

        Args:
            content: The agent output content to check

        Returns:
            CompletionSignal with detection results
        """
        return self._signal_detector.detect(content)

    def _enhance_result_with_completion_signal(
        self, result: Dict[str, Any], content: str
    ) -> Dict[str, Any]:
        """
        Enhance execution result with completion signal information.

        Adds completion signal detection to any execution result,
        allowing consistent signal handling across all execution types.

        Args:
            result: The execution result dictionary
            content: The agent output content

        Returns:
            Enhanced result with completion signal info
        """
        signal = self.detect_completion_signal(content)

        if signal.detected:
            result["completion_signal"] = signal.to_dict()
            result["completion_detected"] = True
            result["completion_type"] = (
                signal.signal_type.name if signal.signal_type else None
            )
            result["completion_text"] = signal.signal_text

            logger.debug(
                f"Completion signal detected: type={signal.signal_type.name}, "
                f"text='{signal.signal_text[:50] if signal.signal_text else ''}...'"
            )
        else:
            result["completion_signal"] = None
            result["completion_detected"] = False

        return result

    async def _execute_issue_response(self, work_item: Dict) -> Dict:
        """Execute an issue response task using IssueResponderProfile"""
        # Extract issue data from work_item context
        context = work_item.get("context", {})
        issue_data = context.get("github_issue", {})
        repo = context.get("repo", "")

        # Load config
        config = IssueResponderConfig.load_from_file(".sugar/config.yaml")

        # Create profile with config settings
        profile = IssueResponderProfile()
        profile.config.settings["max_response_length"] = config.max_response_length
        profile.config.settings["auto_post_threshold"] = config.auto_post_threshold

        # Process input
        input_data = {"issue": issue_data, "repo": repo}
        processed = await profile.process_input(input_data)

        # Execute with agent
        agent_config = SugarAgentConfig(
            model=config.model,
            permission_mode="default",
            allowed_tools=["Read", "Glob", "Grep"],
            system_prompt_additions=profile.get_system_prompt({"repo": repo}),
        )

        agent = SugarAgent(agent_config)
        await agent.start_session()

        try:
            response = await agent.execute(processed["prompt"])
            output_data = {"content": response.content}
            result = await profile.process_output(output_data)

            response_data = result.get("response", {})
            confidence = response_data.get("confidence", 0.0)
            content = response_data.get("content", "")

            # Check if should auto-post
            should_post = confidence >= config.auto_post_threshold

            if should_post:
                # Post to GitHub
                client = GitHubClient(repo=repo)
                client.post_comment(issue_data["number"], content)

                # Record response
                manager = IssueResponseManager()
                await manager.initialize()
                await manager.record_response(
                    repo=repo,
                    issue_number=issue_data["number"],
                    response_type="initial",
                    confidence=confidence,
                    response_content=content,
                    labels_applied=response_data.get("suggested_labels", []),
                    was_auto_posted=True,
                    work_item_id=work_item.get("id"),
                )

            return {
                "success": True,
                "posted": should_post,
                "confidence": confidence,
                "content": content,
            }
        finally:
            await agent.end_session()

    async def _execute_ralph_iteration(
        self, work_item: Dict[str, Any]
    ) -> Dict[str, Any]:
        """Execute a work item with Ralph Wiggum iterative loop."""
        context = work_item.get("context", {})
        max_iterations = context.get("max_iterations", 10)

        # Create Ralph config
        ralph_config = RalphConfig(
            max_iterations=max_iterations,
            completion_promise=context.get("completion_promise", "DONE"),
            require_completion_criteria=False,  # Already validated at CLI
            quality_gates_enabled=self.quality_gates_enabled,
            stop_on_gate_failure=context.get("stop_on_gate_failure", False),
        )

        profile = RalphWiggumProfile(ralph_config=ralph_config)

        # Validate input
        input_data = {
            "prompt": work_item.get("description", work_item.get("title", "")),
            "work_item": work_item,
        }
        processed = await profile.process_input(input_data)

        if not processed.get("valid", True):
            return {
                "success": False,
                "error": "Invalid Ralph input: "
                + ", ".join(processed.get("errors", [])),
                "timestamp": datetime.now(timezone.utc).isoformat(),
                "work_item_id": work_item.get("id"),
            }

        start_time = datetime.now(timezone.utc)
        agent = await self._get_agent()
        prompt = processed.get("prompt", work_item.get("description", ""))

        iteration_results = []
        final_result = None

        logger.info(f"Starting Ralph execution with max {max_iterations} iterations")

        while profile.should_continue():
            iteration = profile.current_iteration + 1  # 1-indexed for display
            logger.info(f"Ralph iteration {iteration}/{max_iterations}")

            try:
                # Execute the prompt
                response = await agent.execute(prompt)

                # Process output to check for completion
                output_data = {
                    "content": response.content,
                    "success": response.success,
                    "files_changed": getattr(response, "files_changed", []),
                }

                result = await profile.process_output(output_data)

                iteration_results.append(
                    {
                        "iteration": iteration,
                        "complete": result.get("complete", False),
                        "promise_detected": result.get("promise_detected"),
                        "stuck": result.get("stuck", False),
                    }
                )

                if result.get("complete"):
                    logger.info(f"Ralph completed after {iteration} iterations")
                    final_result = {
                        "success": True,
                        "content": response.content,
                        "iterations": iteration,
                        "completion_reason": result.get(
                            "completion_reason", "promise_detected"
                        ),
                    }
                    break

                if result.get("stuck"):
                    logger.warning(
                        f"Ralph detected stuck state at iteration {iteration}"
                    )
                    final_result = {
                        "success": False,
                        "error": "Task appears stuck",
                        "iterations": iteration,
                        "stuck_reason": result.get("stuck_reason"),
                    }
                    break

            except Exception as e:
                logger.error(f"Ralph iteration {iteration} failed: {e}")
                iteration_results.append(
                    {
                        "iteration": iteration,
                        "error": str(e),
                    }
                )

        # If loop ended without completion
        if final_result is None:
            final_result = {
                "success": False,
                "error": f"Max iterations ({max_iterations}) reached without completion",
                "iterations": profile.current_iteration,
            }

        execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
        stats = profile.get_iteration_stats()

        return {
            **final_result,
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "work_item_id": work_item.get("id"),
            "execution_time": execution_time,
            "executor": "agent_sdk_ralph",
            "ralph_stats": stats,
            "iteration_results": iteration_results,
        }

    async def execute_work(
        self,
        work_item: Dict[str, Any],
        task_type_info: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """
        Execute a work item using the Claude Agent SDK.

        Args:
            work_item: Work item dictionary
            task_type_info: Optional task type information from database
                           (includes model_tier and complexity_level)

        Returns:
            Result dictionary compatible with Sugar's workflow
        """
        # Check for specialized task types
        task_type = work_item.get("type", "")
        context = work_item.get("context", {})

        if task_type == "issue_response":
            return await self._execute_issue_response(work_item)

        # Check for Ralph-enabled tasks
        if context.get("ralph_enabled"):
            logger.info(f"Executing with Ralph Wiggum: {work_item.get('title')}")
            return await self._execute_ralph_iteration(work_item)

        if self.dry_run:
            logger.info(f"DRY RUN: Simulating execution of {work_item.get('title')}")
            return await self._simulate_execution(work_item)

        # Select model based on task complexity (AUTO-001)
        model_selection = self.select_model_for_task(work_item, task_type_info)
        selected_model = model_selection.model

        logger.info(
            f"Model routing: {model_selection.tier.value} tier -> {selected_model} "
            f"(reason: {model_selection.reason})"
        )

        # Apply tool restrictions from task type if provided
        tool_restrictions = self._get_tool_restrictions(task_type_info)
        if tool_restrictions.get("allowed_tools") or tool_restrictions.get(
            "disallowed_tools"
        ):
            logger.info(
                f"Tool restrictions: allowed={tool_restrictions.get('allowed_tools')}, "
                f"disallowed={tool_restrictions.get('disallowed_tools')}"
            )

        # Apply bash permissions from task type if provided
        bash_permissions = (
            task_type_info.get("bash_permissions", []) if task_type_info else []
        )
        if bash_permissions:
            logger.info(
                f"Bash permissions configured: {len(bash_permissions)} patterns"
            )
            logger.debug(f"Bash permission patterns: {bash_permissions}")

        # Extract hooks from task_type_info
        pre_hooks = []
        post_hooks = []
        if task_type_info and self.hooks_enabled:
            pre_hooks = task_type_info.get("pre_hooks", [])
            post_hooks = task_type_info.get("post_hooks", [])

            # Execute pre-hooks
            if pre_hooks:
                logger.info(
                    f"Executing {len(pre_hooks)} pre-hooks for task {work_item.get('id')}"
                )
                hook_result = await self._hook_executor.execute_hooks(
                    pre_hooks, "pre_hooks", work_item
                )

                if not hook_result["success"]:
                    logger.error(f"Pre-hook failed: {hook_result.get('failed_hook')}")
                    return {
                        "success": False,
                        "error": f"Pre-hook failed: {hook_result.get('failed_hook')}",
                        "hook_result": hook_result,
                        "timestamp": datetime.now(timezone.utc).isoformat(),
                        "work_item_id": work_item.get("id"),
                        "executor": "agent_sdk",
                        "output": "",
                        "files_changed": [],
                        "actions_taken": [],
                        "summary": "Task cancelled due to pre-hook failure",
                    }

        start_time = datetime.now(timezone.utc)

        # Setup thinking capture if enabled
        thinking_capture = None
        if self.thinking_capture_enabled:
            thinking_capture = ThinkingCapture(
                task_id=work_item.get("id", "unknown"),
                task_title=work_item.get("title", ""),
            )

        try:
            # Get agent with the selected model, tool restrictions, and bash permissions
            agent = await self._get_agent(
                model=selected_model,
                tool_restrictions=tool_restrictions,
                bash_permissions=bash_permissions,
            )

            # Attach thinking capture to agent if enabled
            if thinking_capture:
                agent.set_thinking_capture(thinking_capture)

            result = await agent.execute_work_item(work_item)

            execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()

            # Enhance result with executor metadata
            result["executor"] = "agent_sdk"
            result["model"] = selected_model
            result["model_tier"] = model_selection.tier.value
            result["model_routing_reason"] = model_selection.reason
            result["execution_time"] = execution_time

            # Add thinking capture metadata if enabled
            if thinking_capture:
                result["thinking_summary"] = thinking_capture.get_summary()
                result["thinking_log_path"] = thinking_capture.get_thinking_log_path()
                result["thinking_stats"] = thinking_capture.get_stats()

            # Detect completion signals in all executions
            content = result.get("content", result.get("output", ""))
            if content:
                result = self._enhance_result_with_completion_signal(result, content)

            logger.info(
                f"Task completed in {execution_time:.2f}s using {selected_model}: "
                f"{work_item.get('title', 'unknown')}"
            )

            # Execute post-hooks
            if post_hooks and self.hooks_enabled:
                logger.info(
                    f"Executing {len(post_hooks)} post-hooks for task {work_item.get('id')}"
                )
                hook_result = await self._hook_executor.execute_hooks(
                    post_hooks, "post_hooks", work_item
                )

                result["post_hook_result"] = hook_result

                if not hook_result["success"]:
                    logger.warning(
                        f"Post-hook failed: {hook_result.get('failed_hook')}"
                    )
                    result["post_hook_failed"] = True
                    result["post_hook_error"] = hook_result.get("failed_hook")
                    # Mark for review but don't fail the task
                    result["needs_review"] = True
                    result["review_reason"] = "Post-hook validation failed"
                else:
                    result["post_hook_failed"] = False

            return result

        except Exception as e:
            execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
            logger.error(f"Agent SDK execution failed: {e}")

            return {
                "success": False,
                "error": str(e),
                "timestamp": datetime.now(timezone.utc).isoformat(),
                "work_item_id": work_item.get("id"),
                "execution_time": execution_time,
                "executor": "agent_sdk",
                "model": selected_model,
                "model_tier": model_selection.tier.value,
                "output": "",
                "files_changed": [],
                "actions_taken": [],
                "summary": f"Execution failed: {e}",
                "completion_signal": None,
                "completion_detected": False,
            }

    async def validate(self) -> bool:
        """
        Validate that the Agent SDK executor is properly configured.

        Returns:
            True if ready, False otherwise
        """
        try:
            # Try to import SDK
            from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

            logger.info("Claude Agent SDK is available")
            return True

        except ImportError as e:
            logger.error(f"Claude Agent SDK not installed: {e}")
            logger.error("Install with: pip install claude-agent-sdk")
            return False

        except Exception as e:
            logger.error(f"Agent SDK validation failed: {e}")
            return False

    async def start_session(self) -> None:
        """Start an agent session for continuous execution"""
        if not self._session_active:
            agent = await self._get_agent()
            await agent.start_session()
            self._session_active = True
            logger.info("Agent session started")

    async def end_session(self) -> None:
        """End the current agent session"""
        if self._agent and self._session_active:
            await self._agent.end_session()
            self._session_active = False
            logger.info("Agent session ended")

    async def execute_batch(
        self, work_items: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """
        Execute multiple work items in a single session.

        This maintains context between tasks within the same session.

        Args:
            work_items: List of work items to execute

        Returns:
            List of results for each work item
        """
        results = []

        try:
            await self.start_session()

            for work_item in work_items:
                result = await self.execute_work(work_item)
                results.append(result)

                # Check if we should stop (e.g., on failure)
                if not result.get("success") and self.config.get(
                    "stop_on_failure", False
                ):
                    logger.warning("Stopping batch execution due to failure")
                    break

        finally:
            await self.end_session()

        return results

    def get_execution_history(self) -> List[Dict[str, Any]]:
        """Get execution history from the current session"""
        if self._agent:
            return self._agent.get_execution_history()
        return []

    def get_model_router(self) -> ModelRouter:
        """Get the model router instance for external access"""
        return self._model_router

    def get_available_models(self) -> Dict[str, str]:
        """Get all configured model mappings"""
        return self._model_router.get_available_models()

    async def __aenter__(self) -> "AgentSDKExecutor":
        """Async context manager entry"""
        await self.start_session()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        """Async context manager exit"""
        await self.end_session()
