"""
Main filter module for payload-guard.
Provides the Guard class and builder functions.
"""

from __future__ import annotations
from typing import Any, Callable, Optional, Union

from .types import (
    FieldConfig,
    ShapeDescriptor,
    GuardConfig,
    PrimitiveType,
    ErrorCodes,
    ErrorMessage,
    CompiledFilter,
    ShapeBuilder,
    StringBuilder,
    NumberBuilder,
    BooleanBuilder,
    AnyBuilder,
)
from .compiler import compile_shape, CompileOptions
from .security import DEFAULT_SENSITIVE_FIELDS


# Global configuration
_global_config = GuardConfig()


def config(**kwargs) -> None:
    """
    Configure global guard settings.

    Example:
        guard.config(dev_mode=True, strict=True)
    """
    global _global_config
    for key, value in kwargs.items():
        if hasattr(_global_config, key):
            setattr(_global_config, key, value)


def get_config() -> GuardConfig:
    """Get current global configuration."""
    return _global_config


def _create_builder(
    prim_type: PrimitiveType,
) -> Union[StringBuilder, NumberBuilder, BooleanBuilder, AnyBuilder]:
    """Create a builder for the specified primitive type."""
    config_obj = FieldConfig(type=prim_type)

    def builder(value: Any) -> Any:
        # Real logic is in compile()
        return value

    # Mark as builder
    builder.__is_builder = True  # type: ignore
    builder.config = config_obj  # type: ignore

    # Common methods
    def required() -> Any:
        config_obj.required = True
        return builder

    def optional() -> Any:
        config_obj.required = False
        return builder

    def default(val: Any) -> Any:
        config_obj.default = val
        return builder

    def transform(fn: Callable[[Any], Any]) -> Any:
        config_obj.transform = fn
        return builder

    def validate(fn: Callable[[Any], bool]) -> Any:
        config_obj.validate = fn
        return builder

    def from_(key: str) -> Any:
        config_obj.from_field = key
        return builder

    def error(msg: ErrorMessage) -> Any:
        config_obj.error_message = msg
        return builder

    def error_codes(codes: Union[ErrorCodes, dict]) -> Any:
        if isinstance(codes, dict):
            codes = ErrorCodes(**codes)
        config_obj.error_codes = codes
        return builder

    # Attach common methods
    builder.required = required  # type: ignore
    builder.optional = optional  # type: ignore
    builder.default = default  # type: ignore
    builder.transform = transform  # type: ignore
    builder.validate = validate  # type: ignore
    builder.from_ = from_  # type: ignore
    builder.error = error  # type: ignore
    builder.error_codes = error_codes  # type: ignore

    # Type-specific methods
    if prim_type == "string":

        def min(length: int) -> Any:
            config_obj.min = length
            return builder

        def max(length: int) -> Any:
            config_obj.max = length
            return builder

        def email() -> Any:
            config_obj.email = True
            return builder

        def regex(pattern: str) -> Any:
            config_obj.regex = pattern
            return builder

        def trim() -> Any:
            prev_transform = config_obj.transform

            def combined(v: Any) -> Any:
                val = v.strip() if isinstance(v, str) else v
                return prev_transform(val) if prev_transform else val

            config_obj.transform = combined
            return builder

        def to_lower() -> Any:
            prev_transform = config_obj.transform

            def combined(v: Any) -> Any:
                val = v.lower() if isinstance(v, str) else v
                return prev_transform(val) if prev_transform else val

            config_obj.transform = combined
            return builder

        def to_upper() -> Any:
            prev_transform = config_obj.transform

            def combined(v: Any) -> Any:
                val = v.upper() if isinstance(v, str) else v
                return prev_transform(val) if prev_transform else val

            config_obj.transform = combined
            return builder

        builder.min = min  # type: ignore
        builder.max = max  # type: ignore
        builder.email = email  # type: ignore
        builder.regex = regex  # type: ignore
        builder.trim = trim  # type: ignore
        builder.to_lower = to_lower  # type: ignore
        builder.to_upper = to_upper  # type: ignore

    elif prim_type == "number":

        def min(value: Union[int, float]) -> Any:
            config_obj.min = value
            return builder

        def max(value: Union[int, float]) -> Any:
            config_obj.max = value
            return builder

        def integer() -> Any:
            def is_int(v: Any) -> bool:
                return isinstance(v, int) and not isinstance(v, bool)

            config_obj.validate = is_int
            return builder

        def positive() -> Any:
            config_obj.min = 0
            return builder

        builder.min = min  # type: ignore
        builder.max = max  # type: ignore
        builder.integer = integer  # type: ignore
        builder.positive = positive  # type: ignore

    return builder  # type: ignore


def build_shape(
    shape: ShapeDescriptor,
    opts: Optional[GuardConfig] = None,
) -> Callable[[Any], Any]:
    """
    Build a shape filter function from a shape descriptor.

    Args:
        shape: Shape descriptor
        opts: Optional guard configuration

    Returns:
        Filter function with compile method attached
    """
    # Merge options
    sensitive = []
    if opts and opts.sensitive_fields:
        sensitive.extend(opts.sensitive_fields)
    if _global_config.sensitive_fields:
        sensitive.extend(_global_config.sensitive_fields)

    compile_opts = CompileOptions(
        sensitive=sensitive,
        dev=opts.dev_mode if opts else _global_config.dev_mode,
        strict=opts.strict if opts else _global_config.strict,
        redact=opts.redact if opts else _global_config.redact,
        logger=opts.logger if opts else _global_config.logger,
        max_array_length=opts.max_array_length if opts else _global_config.max_array_length,
        collect_errors=opts.collect_errors if opts else _global_config.collect_errors,
    )

    compiled_fn = compile_shape(shape, compile_opts)

    def wrapper(value: Any) -> Any:
        return compiled_fn(value)

    wrapper.compile = compiled_fn  # type: ignore
    wrapper._is_compiled_shape = True  # type: ignore

    def extend(extra: ShapeDescriptor) -> Any:
        """Extend the shape with additional fields."""
        if isinstance(shape, dict) and isinstance(extra, dict):
            merged = {**shape, **extra}
            return build_shape(merged, opts)
        return build_shape(shape, opts)

    wrapper.extend = extend  # type: ignore

    return wrapper


def array(item: ShapeDescriptor) -> dict:
    """
    Create an array shape descriptor.

    Args:
        item: Item shape descriptor

    Returns:
        Array shape descriptor
    """
    return {"__is_array": True, "item": item}


class Guard:
    """
    Main Guard class for payload filtering and validation.

    Example:
        guard = Guard()
        shape = guard.shape({
            'name': 'string',
            'email': guard.string().email(),
        })
        result = shape(data)
    """

    def __init__(self, config: Optional[GuardConfig] = None):
        """Initialize Guard with optional configuration."""
        self._config = config or GuardConfig()

    def shape(self, shape: ShapeDescriptor, opts: Optional[GuardConfig] = None) -> Callable[[Any], Any]:
        """
        Create a shape filter.

        Args:
            shape: Shape descriptor
            opts: Optional configuration

        Returns:
            Filter function
        """
        merged_opts = self._config
        if opts:
            for key, value in vars(opts).items():
                if value is not None:
                    setattr(merged_opts, key, value)
        return build_shape(shape, merged_opts)

    def string(self) -> StringBuilder:
        """Create a string builder."""
        return _create_builder("string")  # type: ignore

    def number(self) -> NumberBuilder:
        """Create a number builder."""
        return _create_builder("number")  # type: ignore

    def boolean(self) -> BooleanBuilder:
        """Create a boolean builder."""
        return _create_builder("boolean")  # type: ignore

    def any(self) -> AnyBuilder:
        """Create an any type builder."""
        return _create_builder("any")  # type: ignore

    def array(self, item: ShapeDescriptor) -> dict:
        """Create an array shape descriptor."""
        return array(item)

    def compile(self, descriptor: ShapeDescriptor, opts: Optional[GuardConfig] = None) -> CompiledFilter:
        """Compile a shape descriptor directly."""
        merged_opts = self._config
        if opts:
            for key, value in vars(opts).items():
                if value is not None:
                    setattr(merged_opts, key, value)

        compile_opts = CompileOptions(
            sensitive=merged_opts.sensitive_fields,
            dev=merged_opts.dev_mode,
            strict=merged_opts.strict,
            redact=merged_opts.redact,
            logger=merged_opts.logger,
            max_array_length=merged_opts.max_array_length,
            collect_errors=merged_opts.collect_errors,
        )
        return compile_shape(descriptor, compile_opts)

    def config(self, **kwargs) -> None:
        """Update guard configuration."""
        for key, value in kwargs.items():
            if hasattr(self._config, key):
                setattr(self._config, key, value)


# Default guard instance
guard = Guard()

# Convenience exports
default_sensitive = DEFAULT_SENSITIVE_FIELDS
