"""Enhanced Claude Code agent with tracer integration for PentestGPT."""

import logging
import re
from pathlib import Path
from typing import Any, ClassVar

from claude_agent_sdk import (
    AssistantMessage,
    ClaudeAgentOptions,
    ClaudeSDKClient,
    CLIConnectionError,
    CLINotFoundError,
    ResultMessage,
    TextBlock,
    ToolUseBlock,
)

from pentestgpt.core.config import PentestGPTConfig
from pentestgpt.core.tracer import Tracer, get_global_tracer
from pentestgpt.prompts.pentesting import get_ctf_prompt

# Logging setup - will be initialized lazily when PentestAgent is first instantiated
DEBUG_LOG_WORKSPACE = Path("/workspace/pentestgpt-debug.log")
DEBUG_LOG_FALLBACK = Path("/tmp/pentestgpt-debug.log")
DEBUG_LOG = DEBUG_LOG_FALLBACK  # Default to fallback, will be updated by _setup_agent_logging()
_logging_initialized = False
logger = logging.getLogger(__name__)


def _setup_agent_logging() -> None:
    """Set up debug logging to file (called once when first PentestAgent is created)."""
    global _logging_initialized, DEBUG_LOG
    if _logging_initialized:
        return

    handlers: list[logging.Handler] = [logging.StreamHandler()]  # Always log to stderr
    try:
        # Try to create log file in workspace
        handlers.append(logging.FileHandler(DEBUG_LOG_WORKSPACE, mode="w"))
        DEBUG_LOG = DEBUG_LOG_WORKSPACE
    except (PermissionError, OSError):
        # Fallback to /tmp if workspace is not writable
        handlers.append(logging.FileHandler(DEBUG_LOG_FALLBACK, mode="w"))
        DEBUG_LOG = DEBUG_LOG_FALLBACK
        print(
            f"Warning: Could not write to {DEBUG_LOG_WORKSPACE}, using {DEBUG_LOG} instead",
            flush=True,
        )

    logging.basicConfig(
        level=logging.DEBUG,
        format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
        handlers=handlers,
    )
    _logging_initialized = True


class PentestAgent:
    """Enhanced Claude Code agent with activity tracking and streaming updates."""

    # Flag detection patterns
    FLAG_PATTERNS: ClassVar[list[str]] = [
        r"flag\{[^\}]+\}",  # flag{...}
        r"FLAG\{[^\}]+\}",  # FLAG{...}
        r"HTB\{[^\}]+\}",  # HTB{...}
        r"CTF\{[^\}]+\}",  # CTF{...}
        r"[A-Za-z0-9_]+\{[^\}]+\}",  # Generic CTF format
        r"\b[a-f0-9]{32}\b",  # 32-char hex (HTB user/root flags)
    ]

    def __init__(
        self,
        config: PentestGPTConfig,
        tracer: Tracer | None = None,
    ) -> None:
        """
        Initialize the pentest agent.

        Args:
            config: Configuration for the agent
            tracer: Optional tracer instance (uses global if not provided)
        """
        # Set up logging (only happens once, on first agent instantiation)
        _setup_agent_logging()

        self.config = config
        self.tracer = tracer or get_global_tracer()
        self.client: ClaudeSDKClient | None = None
        self.walkthrough_steps: list[str] = []
        self.flags_found: list[dict[str, str]] = []

    async def execute(self, task: str) -> dict[str, Any]:
        """
        Execute a penetration testing task.

        Args:
            task: The task/instructions for the agent

        Returns:
            Dictionary with execution results
        """
        logger.debug("=" * 80)
        logger.debug(f"EXECUTE START - Task: {task[:100]}...")
        self.tracer.track_agent_status("starting", "Initializing agent")

        try:
            # Setup Claude Code client
            logger.debug("Creating ClaudeAgentOptions...")
            options = ClaudeAgentOptions(
                cwd=str(self.config.working_directory),
                permission_mode=self.config.permission_mode,  # type: ignore[arg-type]
                system_prompt=self._build_system_prompt(),
                model=self.config.llm_model,
            )
            logger.debug(f"Options created: cwd={options.cwd}, model={options.model}")

            logger.debug("Creating ClaudeSDKClient...")
            self.client = ClaudeSDKClient(options=options)

            logger.debug("Attempting to connect to Claude Code CLI...")
            await self.client.connect()
            logger.debug("✓ Successfully connected to Claude Code CLI")

            self.tracer.track_agent_status("connected", "Connected to Claude Code")

            # Send the task
            logger.debug(f"Sending query to Claude Code: {task[:100]}...")
            await self.client.query(task)
            logger.debug("✓ Query sent, waiting for responses...")
            self.tracer.track_message(f"Task submitted: {task[:100]}...", message_type="info")

            # Process responses
            logger.debug("Starting message processing loop...")
            messages = []
            output_parts = []
            current_tool_id: int | None = None
            message_count = 0

            async for message in self.client.receive_response():
                message_count += 1
                logger.debug(f"← Received message #{message_count}: {type(message).__name__}")
                messages.append(message)

                if isinstance(message, AssistantMessage):
                    logger.debug(f"  AssistantMessage with {len(message.content)} blocks")
                    for block in message.content:
                        if isinstance(block, TextBlock):
                            logger.debug(f"  TextBlock: {block.text[:50]}...")
                            output_parts.append(block.text)

                            # Detect flags in the text
                            detected_flags = self._detect_flags(block.text)
                            if detected_flags:
                                for flag in detected_flags:
                                    flag_msg = f"🚩 FLAG DETECTED: {flag}"
                                    logger.info(flag_msg)
                                    self.tracer.track_message(flag_msg, message_type="success")
                                    self.flags_found.append(
                                        {"flag": flag, "context": block.text[:200]}
                                    )

                            self.tracer.track_message(block.text, message_type="info")

                            # Track as walkthrough step if it's substantial
                            if len(block.text.strip()) > 20:
                                self._add_walkthrough_step(block.text)

                        elif isinstance(block, ToolUseBlock):
                            logger.debug(f"  ToolUseBlock: {block.name}")
                            # Track tool execution start
                            current_tool_id = self.tracer.track_tool_start(
                                tool_name=block.name,
                                args=block.input,
                            )

                elif isinstance(message, ResultMessage):
                    logger.debug("  ResultMessage - Task completed")
                    # Mark last tool as completed if there was one
                    if current_tool_id is not None:
                        self.tracer.track_tool_complete(current_tool_id, status="completed")

                    # Track completion
                    cost = getattr(message, "total_cost_usd", 0)
                    logger.debug(f"  Total cost: ${cost:.4f}")
                    self.tracer.track_agent_status(
                        "completed", f"Task completed | Cost: ${cost:.4f}"
                    )

            logger.debug(f"Message loop ended. Processed {message_count} messages total.")

            result = {
                "success": True,
                "messages": messages,
                "output": "\n".join(output_parts),
                "cost_usd": getattr(messages[-1], "total_cost_usd", 0) if messages else 0,
                "walkthrough": self.walkthrough_steps,
                "flags_found": self.flags_found,
            }
            output_str = str(result["output"])
            logger.debug(
                f"✓ EXECUTE COMPLETE - Success: {result['success']}, Output length: {len(output_str)}"
            )
            logger.debug(f"  Flags found: {len(self.flags_found)}")
            logger.debug(f"  Walkthrough steps: {len(self.walkthrough_steps)}")
            return result

        except CLINotFoundError as e:
            logger.error(f"✗ CLINotFoundError: {e}")
            error_msg = (
                "Claude Code CLI not found. Install with: npm install -g @anthropic-ai/claude-code"
            )
            self.tracer.track_message(error_msg, message_type="error")
            return {"success": False, "error": error_msg}

        except CLIConnectionError as e:
            logger.error(f"✗ CLIConnectionError: {e}")
            error_msg = f"Failed to connect to Claude Code: {e!s}"
            self.tracer.track_message(error_msg, message_type="error")
            return {"success": False, "error": error_msg}

        except Exception as e:
            logger.error(f"✗ Unexpected Exception: {type(e).__name__}: {e}", exc_info=True)
            error_msg = f"Unexpected error: {e!s}"
            self.tracer.track_message(error_msg, message_type="error")
            return {"success": False, "error": error_msg}

        finally:
            logger.debug("Cleanup: disconnecting client...")
            if self.client:
                await self.client.disconnect()
                logger.debug("✓ Client disconnected")
            self.tracer.track_agent_status("disconnected", "Agent stopped")
            logger.debug("=" * 80)

    def _build_system_prompt(self) -> str:
        """Build the CTF system prompt for the agent."""
        return get_ctf_prompt(self.config.custom_instruction)

    def _detect_flags(self, text: str) -> list[str]:
        """
        Detect potential flags in text using regex patterns.

        Args:
            text: Text to search for flags

        Returns:
            List of detected flag strings
        """
        flags = []
        for pattern in self.FLAG_PATTERNS:
            matches = re.finditer(pattern, text, re.IGNORECASE)
            for match in matches:
                flag = match.group(0)
                # Avoid duplicates
                if flag not in flags:
                    flags.append(flag)
        return flags

    def _add_walkthrough_step(self, step: str) -> None:
        """
        Add a step to the walkthrough.

        Args:
            step: Description of the step taken
        """
        # Clean up the step text
        step = step.strip()
        if step and step not in self.walkthrough_steps:
            self.walkthrough_steps.append(step)
            logger.debug(f"Walkthrough step added: {step[:100]}...")


async def run_pentest(
    target: str,
    custom_instruction: str | None = None,
    model: str | None = None,
    working_dir: str | None = None,
    debug: bool = False,
    resume_session: str | None = None,
) -> dict[str, Any]:
    """
    Convenience function to run a CTF challenge or penetration test.

    Uses the new AgentController for lifecycle management and session persistence.

    Args:
        target: Target challenge/machine to solve
        custom_instruction: Optional custom challenge context or instructions
        model: Optional model override
        working_dir: Optional working directory override
        debug: Enable debug mode with verbose console output
        resume_session: Optional session ID to resume

    Returns:
        Dictionary with challenge results including walkthrough and flags found
    """
    from pentestgpt.core.config import load_config
    from pentestgpt.core.controller import AgentController

    # Enable verbose console logging in debug mode
    if debug:
        logger.info("=" * 80)
        logger.info("DEBUG MODE ENABLED - CTF CHALLENGE SOLVER")
        logger.info(f"Debug log file: {DEBUG_LOG}")
        logger.info(f"Target: {target}")
        if custom_instruction:
            logger.info(f"Challenge context: {custom_instruction}")
        if model:
            logger.info(f"Model: {model}")
        if resume_session:
            logger.info(f"Resuming session: {resume_session}")
        logger.info("=" * 80)

    # Build config
    config_kwargs: dict[str, Any] = {"target": target}
    if custom_instruction:
        config_kwargs["custom_instruction"] = custom_instruction
    if model:
        config_kwargs["llm_model"] = model
    if working_dir:
        config_kwargs["working_directory"] = Path(working_dir)

    config = load_config(**config_kwargs)

    # Build task
    task = f"Solve this CTF challenge and capture the flag(s): {target}"
    if custom_instruction:
        task += f"\n\nChallenge context: {custom_instruction}"

    # Use AgentController for lifecycle management
    controller = AgentController(config)
    result = await controller.run(task, resume_session_id=resume_session)

    # Map result to legacy format for backward compatibility
    if result.get("success"):
        # Convert flags_found from list of strings to list of dicts if needed
        flags = result.get("flags_found", [])
        if flags and isinstance(flags[0], str):
            flags = [{"flag": f, "context": ""} for f in flags]

        result = {
            "success": True,
            "output": result.get("output", ""),
            "cost_usd": result.get("cost_usd", 0),
            "flags_found": flags,
            "walkthrough": [],  # Walkthrough tracking moved to session
            "session_id": result.get("session_id", ""),
        }

    if debug:
        logger.info("=" * 80)
        logger.info(f"CHALLENGE COMPLETE - Success: {result.get('success')}")
        if result.get("success"):
            logger.info(f"Flags found: {len(result.get('flags_found', []))}")
            logger.info(f"Cost: ${result.get('cost_usd', 0):.4f}")
            logger.info(f"Session: {result.get('session_id', 'N/A')}")
            for flag_data in result.get("flags_found", []):
                flag = (
                    flag_data.get("flag", flag_data) if isinstance(flag_data, dict) else flag_data
                )
                logger.info(f"  🚩 {flag}")
        else:
            logger.error(f"Error: {result.get('error', 'Unknown')}")
        logger.info("=" * 80)

    return result
