import inspect
import re
from collections.abc import Callable

from .core import CORE_DIRECTIVES, SECURITY_REMINDER
from .instructions import INSTRUCTIONS
from .output import FINAL_OUTPUT_REQUIREMENTS
from .personality import PERSONALITY
from .tools import AVAILABLE_TOOLS_HEADER, TOOL_INSTRUCTIONS

uneditable_prompt = "\n".join(
    [
        CORE_DIRECTIVES.strip(),
        PERSONALITY.strip(),
        INSTRUCTIONS.strip(),
        TOOL_INSTRUCTIONS.strip(),
        FINAL_OUTPUT_REQUIREMENTS.strip(),
    ]
)


def make_safe_function_name(name: str) -> str:
    """Convert a tool name to a valid Python function name."""
    # Replace non-alphanumeric characters with underscores
    safe_name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
    # Ensure the name doesn't start with a digit
    if safe_name and safe_name[0].isdigit():
        safe_name = f"tool_{safe_name}"
    # Handle empty name edge case
    if not safe_name:
        safe_name = "unnamed_tool"
    return safe_name


# Compile regex once for better performance
_RAISES_PATTERN = re.compile(r"\n\s*[Rr]aises\s*:.*$", re.DOTALL)


def _clean_docstring(docstring: str | None) -> str:
    """Remove the 'Raises:' section and everything after it from a docstring."""
    if not docstring:
        return ""

    # Use pre-compiled regex for better performance
    cleaned = _RAISES_PATTERN.sub("", docstring)
    return cleaned.strip()


def build_tool_definitions(tools: list[Callable]) -> tuple[list[str], dict[str, Callable]]:
    tool_definitions = []
    context = {}

    # Pre-allocate lists for better performance
    tool_definitions = [None] * len(tools)
    for i, tool in enumerate(tools):
        tool_name = tool.__name__
        cleaned_docstring = _clean_docstring(tool.__doc__)

        # Pre-compute string parts to avoid repeated string operations
        async_prefix = "async " if inspect.iscoroutinefunction(tool) else ""
        signature = str(inspect.signature(tool))

        tool_definitions[i] = f'''{async_prefix}def {tool_name} {signature}:
    """{cleaned_docstring}"""
    ...'''
        context[tool_name] = tool

    return tool_definitions, context


def create_default_prompt(
    base_prompt: str | None = None,
    agent: object | None = None,
    is_initial_prompt: bool = False,
    agent_type: str = "agent",
):
    if is_initial_prompt:
        system_prompt = uneditable_prompt.strip()
    else:
        system_prompt = ""

    if is_initial_prompt:
        if base_prompt and base_prompt.strip():
            system_prompt += (
                f"\n\nUse the following information/instructions while completing your tasks:\n\n{base_prompt}"
            )

        # Append existing agent (plan + code) if provided
        try:
            if agent and hasattr(agent, "instructions"):
                pb = agent.instructions or {}
                plan = pb.get("plan")
                code = pb.get("script")
                if plan or code:
                    if agent_type == "template":
                        system_prompt += "\n\n### PRE-LOADED TEMPLATE CONTEXT\n"
                        system_prompt += "The following template plan and code have ALREADY been executed and loaded into the environment. "
                    else:
                        system_prompt += "\n\n### PRE-LOADED AGENT CONTEXT\n"
                        system_prompt += "The following agent plan and code have ALREADY been executed and loaded into the environment. "

                    system_prompt += "The functions are active in the global namespace. \n"

                    if plan:
                        if isinstance(plan, list):
                            plan_block = "\n".join(f"- {str(s)}" for s in plan)
                        else:
                            plan_block = str(plan)
                        system_prompt += f"**Plan Steps:**\n{plan_block}\n"

                    if code:
                        system_prompt += f"\n**Reference Code (DO NOT REDEFINE):**\n```python\n{str(code)}\n```\n"

                        system_prompt += """
                        CRITICAL: The functions above are ALREADY DEFINED. DO NOT redefine them in your tool call to execute_python_code, or you will overwrite the active state.
                        The code contains an orchestrator function with commented-out stepwise execution steps.
                        You MUST NOT call the orchestrator function directly.
                        Instead, you MUST execute the steps shown in the comments sequentially, in separate calls to `execute_python_code, to show progress and aid debugging."""

                        if agent_type == "template":
                            system_prompt += "\nCRITICAL: You CANNOT modify this template directly as it is public. You can create a new version owned by you using the agent builder tools (plan_agent/schedule_code_and_save_agent) with the user's permission, if required. To update the template itself, the user must use the agents dashboard. In case there are ANY errors in the code (including execution failures OR logical mistakes), you MUST inform the user and ask if they want you to create a new version of the template with the fixes in their agents dashboard."
                        else:
                            system_prompt += "\nFor modifying the functions, you MUST call the appropriate agent builder tools (plan_agent/schedule_code_and_save_agent), and only with the user's explicit permission/instruction. In case there are ANY errors in the code (including execution failures OR logical mistakes), you MUST inform the user and ask for permission to modify the agent plan/code."

        except Exception:
            # Silently ignore formatting issues
            pass

    return system_prompt


def build_complete_system_instructions(
    base_prompt: str | None = None,
    agent: object | None = None,
    tool_definitions: list[str] | None = None,
    agent_type: str = "agent",
) -> str:
    """
    Build complete system instructions including base prompt, tool definitions, and security reminder.

    This is the main function used by route_entry to build the complete system instructions.

    Args:
        base_prompt: Custom instructions from the agent configuration
        agent: Agent object with plan and script (if loaded from registry)
        tool_definitions: List of tool definition strings (function signatures with docstrings)
        agent_type: Type of the agent ('agent' or 'template')

    Returns:
        Complete system instructions string ready to use
    """
    # Start with the base prompt (includes core directives, personality, etc.)
    instructions = create_default_prompt(
        base_prompt=base_prompt,
        agent=agent,
        is_initial_prompt=True,
        agent_type=agent_type,
    )

    # Add available tools section if tools are provided
    if tool_definitions:
        instructions += "\n" + AVAILABLE_TOOLS_HEADER.strip() + "\n"
        instructions += "\n".join(tool_definitions)

    # Add security reminder at the end
    instructions += "\n" + SECURITY_REMINDER.strip()

    return instructions
