import inspect
import re
from functools import wraps
from typing import Annotated, Optional

from fastapi import Path, Query

from letta.errors import LettaInvalidArgumentError
from letta.schemas.enums import PrimitiveType  # PrimitiveType is now in schemas.enums

# Map from PrimitiveType to the actual prefix string (which is just the enum value)
PRIMITIVE_ID_PREFIXES = {primitive_type: primitive_type.value for primitive_type in PrimitiveType}


PRIMITIVE_ID_PATTERNS = {
    # f-string interpolation gets confused because of the regex's required curly braces {}
    prefix: re.compile("^" + prefix + "-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
    for prefix in PRIMITIVE_ID_PREFIXES.values()
}


def _create_path_validator_factory(primitive: str):
    """
    Creates a factory function that returns a fresh Path validator.

    This avoids shared state issues when the same validator is used
    across multiple endpoints with different parameter names.
    """

    def factory():
        return Path(
            description=f"The ID of the {primitive} in the format '{primitive}-<uuid4>'",
            pattern=PRIMITIVE_ID_PATTERNS[primitive].pattern,
            examples=[f"{primitive}-123e4567-e89b-42d3-8456-426614174000"],
            min_length=len(primitive) + 1 + 36,
            max_length=len(primitive) + 1 + 36,
        )

    return factory


# PATH_VALIDATORS now contains factory functions, not Path objects
# Usage: folder_id: str = PATH_VALIDATORS[PrimitiveType.FOLDER.value]()
PATH_VALIDATORS = {primitive_type.value: _create_path_validator_factory(primitive_type.value) for primitive_type in PrimitiveType}


def _create_conversation_id_or_default_path_validator_factory():
    """Conversation IDs with support for 'default' and agent IDs (backwards compatibility)."""

    conversation_primitive = PrimitiveType.CONVERSATION.value
    agent_primitive = PrimitiveType.AGENT.value
    conversation_pattern = PRIMITIVE_ID_PATTERNS[conversation_primitive].pattern
    agent_pattern = PRIMITIVE_ID_PATTERNS[agent_primitive].pattern
    # Make the full regex accept: conversation ID, agent ID, or 'default'.
    # Patterns already contain ^...$ anchors, so strip them for the alternation.
    conversation_or_agent_or_default_pattern = f"^(default|{conversation_pattern[1:-1]}|{agent_pattern[1:-1]})$"

    def factory():
        return Path(
            description=(
                f"The conversation identifier. Can be a conversation ID ('{conversation_primitive}-<uuid4>'), "
                f"'default' for agent-direct mode (with agent_id parameter), "
                f"or an agent ID ('{agent_primitive}-<uuid4>') for backwards compatibility (deprecated)."
            ),
            pattern=conversation_or_agent_or_default_pattern,
            examples=[
                "default",
                f"{conversation_primitive}-123e4567-e89b-42d3-8456-426614174000",
                f"{agent_primitive}-123e4567-e89b-42d3-8456-426614174000",
            ],
            min_length=1,
            max_length=max(len(conversation_primitive), len(agent_primitive)) + 1 + 36,
        )

    return factory


# Type aliases for common ID types
# These can be used directly in route handler signatures for cleaner code
AgentId = Annotated[str, PATH_VALIDATORS[PrimitiveType.AGENT.value]()]
ToolId = Annotated[str, PATH_VALIDATORS[PrimitiveType.TOOL.value]()]
SourceId = Annotated[str, PATH_VALIDATORS[PrimitiveType.SOURCE.value]()]
BlockId = Annotated[str, PATH_VALIDATORS[PrimitiveType.BLOCK.value]()]
MessageId = Annotated[str, PATH_VALIDATORS[PrimitiveType.MESSAGE.value]()]
RunId = Annotated[str, PATH_VALIDATORS[PrimitiveType.RUN.value]()]
JobId = Annotated[str, PATH_VALIDATORS[PrimitiveType.JOB.value]()]
GroupId = Annotated[str, PATH_VALIDATORS[PrimitiveType.GROUP.value]()]
FileId = Annotated[str, PATH_VALIDATORS[PrimitiveType.FILE.value]()]
FolderId = Annotated[str, PATH_VALIDATORS[PrimitiveType.FOLDER.value]()]
ArchiveId = Annotated[str, PATH_VALIDATORS[PrimitiveType.ARCHIVE.value]()]
PassageId = Annotated[str, PATH_VALIDATORS[PrimitiveType.PASSAGE.value]()]
ProviderId = Annotated[str, PATH_VALIDATORS[PrimitiveType.PROVIDER.value]()]
SandboxConfigId = Annotated[str, PATH_VALIDATORS[PrimitiveType.SANDBOX_CONFIG.value]()]
StepId = Annotated[str, PATH_VALIDATORS[PrimitiveType.STEP.value]()]
IdentityId = Annotated[str, PATH_VALIDATORS[PrimitiveType.IDENTITY.value]()]
ConversationId = Annotated[str, PATH_VALIDATORS[PrimitiveType.CONVERSATION.value]()]

# Conversation ID with support for 'default' and agent IDs (for agent-direct mode endpoints)
# Backwards compatible - agent-* will be deprecated in favor of conversation_id='default' + agent_id param
ConversationIdOrDefault = Annotated[str, _create_conversation_id_or_default_path_validator_factory()()]

# Infrastructure types
McpServerId = Annotated[str, PATH_VALIDATORS[PrimitiveType.MCP_SERVER.value]()]
McpOAuthId = Annotated[str, PATH_VALIDATORS[PrimitiveType.MCP_OAUTH.value]()]
FileAgentId = Annotated[str, PATH_VALIDATORS[PrimitiveType.FILE_AGENT.value]()]

# Configuration types
SandboxEnvId = Annotated[str, PATH_VALIDATORS[PrimitiveType.SANDBOX_ENV.value]()]
AgentEnvId = Annotated[str, PATH_VALIDATORS[PrimitiveType.AGENT_ENV.value]()]

# Core entity types
UserId = Annotated[str, PATH_VALIDATORS[PrimitiveType.USER.value]()]
OrganizationId = Annotated[str, PATH_VALIDATORS[PrimitiveType.ORGANIZATION.value]()]
ToolRuleId = Annotated[str, PATH_VALIDATORS[PrimitiveType.TOOL_RULE.value]()]

# Batch processing types
BatchItemId = Annotated[str, PATH_VALIDATORS[PrimitiveType.BATCH_ITEM.value]()]
BatchRequestId = Annotated[str, PATH_VALIDATORS[PrimitiveType.BATCH_REQUEST.value]()]

# Telemetry types
ProviderTraceId = Annotated[str, PATH_VALIDATORS[PrimitiveType.PROVIDER_TRACE.value]()]


def raise_on_invalid_id(param_name: str, expected_prefix: PrimitiveType):
    """
    Decorator that validates an ID parameter has the expected prefix format.
    Can be stacked multiple times on the same function to validate different IDs.

    Args:
        param_name: The name of the function parameter to validate (e.g., "agent_id")
        expected_prefix: The expected primitive type (e.g., PrimitiveType.AGENT)

    Example:
        @raise_on_invalid_id(param_name="agent_id", expected_prefix=PrimitiveType.AGENT)
        @raise_on_invalid_id(param_name="folder_id", expected_prefix=PrimitiveType.FOLDER)
        def my_function(agent_id: str, folder_id: str):
            pass
    """

    def decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            sig = inspect.signature(function)
            bound_args = sig.bind(*args, **kwargs)
            bound_args.apply_defaults()

            if param_name in bound_args.arguments:
                arg_value = bound_args.arguments[param_name]

                if arg_value is not None:
                    prefix = PRIMITIVE_ID_PREFIXES[expected_prefix]
                    if PRIMITIVE_ID_PATTERNS[prefix].match(arg_value) is None:
                        raise LettaInvalidArgumentError(
                            message=f"Invalid {expected_prefix.value} ID format: {arg_value}. Expected format: '{prefix}-<uuid4>'",
                            argument_name=param_name,
                        )

            return function(*args, **kwargs)

        return wrapper

    return decorator


# =============================================================================
# Query Parameter Validators
# =============================================================================
# Format validators for common query parameters to match frontend constraints


def _create_id_query_validator(primitive: str):
    """
    Creates a Query validator for ID parameters with format validation.

    Args:
        primitive: The primitive type prefix (e.g., "agent", "tool")
    Returns:
        A Query validator with pattern matching
    """
    return Query(
        description=f"The ID of the {primitive} in the format '{primitive}-<uuid4>'",
        pattern=PRIMITIVE_ID_PATTERNS[primitive].pattern,
        examples=[f"{primitive}-123e4567-e89b-42d3-8456-426614174000"],
        min_length=len(primitive) + 1 + 36,
        max_length=len(primitive) + 1 + 36,
    )


# Query parameter ID validators with format checking
AgentIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.AGENT.value)]
ToolIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.TOOL.value)]
SourceIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.SOURCE.value)]
BlockIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.BLOCK.value)]
MessageIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.MESSAGE.value)]
RunIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.RUN.value)]
JobIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.JOB.value)]
GroupIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.GROUP.value)]
IdentityIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.IDENTITY.value)]
UserIdQuery = Annotated[Optional[str], _create_id_query_validator(PrimitiveType.USER.value)]
UserIdQueryRequired = Annotated[str, _create_id_query_validator(PrimitiveType.USER.value)]


# =============================================================================
# String Field Validators
# =============================================================================
# Format validators for common string fields

# Label validator: alphanumeric, hyphens, underscores, max 50 chars
BlockLabelQuery = Annotated[
    Optional[str],
    Query(
        description="Label to include (alphanumeric, hyphens, underscores, forward slashes)",
        pattern=r"^[a-zA-Z0-9_/-]+$",
        min_length=1,
        max_length=50,
        examples=["human", "persona", "the_label_of-a-block", "the_label_of-a-block/with-forward-slash"],
    ),
]


# Name validator: similar to label but allows spaces, max 100 chars
BlockNameQuery = Annotated[
    Optional[str],
    Query(
        description="Name filter (alphanumeric, spaces, hyphens, underscores)",
        pattern=r"^[a-zA-Z0-9 _-]+$",
        min_length=1,
        max_length=100,
        examples=["My Agent", "test_tool", "default-config"],
    ),
]

# Search query validator: general text search, max 200 chars
BlockLabelSearchQuery = Annotated[
    Optional[str],
    Query(
        description="Search blocks by label. If provided, returns blocks whose label matches the search query. This is a full-text search on block labels.",
        pattern=r"^[a-zA-Z0-9_/-]+$",
        min_length=1,
        max_length=50,
        examples=["human", "persona", "the_label_of-a-block", "the_label_of-a-block/with-forward-slash"],
    ),
]

BlockValueSearchQuery = Annotated[
    Optional[str],
    Query(
        description="Search blocks by value. If provided, returns blocks whose value matches the search query. This is a full-text search on block values.",
        min_length=1,
        max_length=200,
    ),
]

BlockDescriptionSearchQuery = Annotated[
    Optional[str],
    Query(
        description="Search blocks by description. If provided, returns blocks whose description matches the search query. This is a full-text search on block descriptions.",
        min_length=1,
        max_length=200,
    ),
]
