"""Scanner registry for Tru-Guard."""

from typing import Any, Callable, TypeVar

from trusys.exceptions import ScannerAlreadyRegisteredError, ScannerNotFoundError
from trusys.scanners.base import BaseScanner
from trusys.types import ScannerMetadata, ScanType
from trusys.utils.helpers import extract_init_parameters

T = TypeVar("T", bound=BaseScanner)


class ScannerRegistry:
    """Registry for scanner classes.

    Provides registration, discovery, and retrieval of scanner classes.
    """

    _scanners: dict[str, type[BaseScanner]] = {}
    _metadata: dict[str, ScannerMetadata] = {}
    _validator_type_map: dict[str, type[BaseScanner]] = {}  # Maps validator_type -> scanner class

    @classmethod
    def register(
        cls,
        name: str,
        scan_types: list[str | ScanType],
        description: str = "",
        validator_type: str | None = None,
    ) -> Callable[[type[T]], type[T]]:
        """Decorator to register a scanner class.

        Args:
            name: Unique name for the scanner
            scan_types: List of scan types the scanner supports
            description: Optional description of the scanner
            validator_type: Validator type string that maps to this scanner (e.g., "regex", "blocklist")

        Returns:
            Decorator function

        Raises:
            ScannerAlreadyRegisteredError: If scanner name is already registered
        """

        def decorator(scanner_class: type[T]) -> type[T]:
            if name in cls._scanners:
                raise ScannerAlreadyRegisteredError(name)

            # Convert string scan types to enums
            enum_scan_types = [
                ScanType(st) if isinstance(st, str) else st for st in scan_types
            ]

            # Set class-level attributes
            scanner_class._registered_name = name
            scanner_class._registered_scan_types = enum_scan_types
            scanner_class._validator_type = validator_type

            # Register the scanner
            cls._scanners[name] = scanner_class

            # Register validator type mapping if provided
            if validator_type:
                if validator_type in cls._validator_type_map:
                    # Allow override but log warning
                    import warnings
                    warnings.warn(
                        f"Validator type '{validator_type}' is already registered. "
                        f"Overriding with {scanner_class.__name__}."
                    )
                cls._validator_type_map[validator_type] = scanner_class

            # Extract and store metadata
            cls._metadata[name] = ScannerMetadata(
                name=name,
                scan_types=enum_scan_types,
                parameters=extract_init_parameters(scanner_class),
                description=description,
            )

            return scanner_class

        return decorator

    @classmethod
    def unregister(cls, name: str) -> None:
        """Unregister a scanner.

        Args:
            name: Name of the scanner to unregister

        Raises:
            ScannerNotFoundError: If scanner is not found
        """
        if name not in cls._scanners:
            raise ScannerNotFoundError(name)

        del cls._scanners[name]
        del cls._metadata[name]

    @classmethod
    def list_scanners(cls) -> list[str]:
        """List all registered scanner names.

        Returns:
            List of registered scanner names
        """
        return list(cls._scanners.keys())

    @classmethod
    def get_scanner(cls, name: str) -> type[BaseScanner]:
        """Get a scanner class by name.

        Args:
            name: Name of the scanner

        Returns:
            The scanner class

        Raises:
            ScannerNotFoundError: If scanner is not found
        """
        if name not in cls._scanners:
            raise ScannerNotFoundError(name)
        return cls._scanners[name]

    @classmethod
    def get_scanner_info(cls, name: str) -> ScannerMetadata:
        """Get scanner metadata by name.

        Args:
            name: Name of the scanner

        Returns:
            Scanner metadata including parameters and scan types

        Raises:
            ScannerNotFoundError: If scanner is not found
        """
        if name not in cls._metadata:
            raise ScannerNotFoundError(name)
        return cls._metadata[name]

    @classmethod
    def get_all_metadata(cls) -> dict[str, ScannerMetadata]:
        """Get metadata for all registered scanners.

        Returns:
            Dictionary mapping scanner names to metadata
        """
        return cls._metadata.copy()

    @classmethod
    def is_registered(cls, name: str) -> bool:
        """Check if a scanner is registered.

        Args:
            name: Name of the scanner

        Returns:
            True if the scanner is registered
        """
        return name in cls._scanners

    @classmethod
    def clear(cls) -> None:
        """Clear all registered scanners.

        Useful for testing.
        """
        cls._scanners.clear()
        cls._metadata.clear()
        cls._validator_type_map.clear()

    @classmethod
    def get_scanner_by_validator_type(cls, validator_type: str) -> type[BaseScanner] | None:
        """Get a scanner class by validator type.

        Args:
            validator_type: The validator type string (e.g., "regex", "blocklist")

        Returns:
            The scanner class, or None if not found
        """
        return cls._validator_type_map.get(validator_type)

    @classmethod
    def create_scanner(cls, name: str, **kwargs: Any) -> BaseScanner:
        """Create a scanner instance by name.

        Args:
            name: Name of the scanner
            **kwargs: Arguments to pass to the scanner constructor

        Returns:
            Scanner instance

        Raises:
            ScannerNotFoundError: If scanner is not found
        """
        scanner_class = cls.get_scanner(name)
        return scanner_class(**kwargs)


def register_scanner(
    name: str,
    scan_types: list[str | ScanType],
    description: str = "",
    validator_type: str | None = None,
) -> Callable[[type[T]], type[T]]:
    """Convenience decorator for registering scanners.

    Args:
        name: Unique name for the scanner
        scan_types: List of scan types the scanner supports
        description: Optional description of the scanner
        validator_type: Validator type string that maps to this scanner (e.g., "regex", "blocklist")

    Returns:
        Decorator function

    Example:
        @register_scanner(name="toxicity", scan_types=["input", "output"], validator_type="toxicity")
        class ToxicityScanner(BaseScanner):
            def scan(self, content, context):
                ...
    """
    return ScannerRegistry.register(name, scan_types, description, validator_type)
