from __future__ import annotations
from typing import Annotated, Any, Callable, Optional, Type, TypeVar
from pydantic import AfterValidator, BeforeValidator, Field, create_model, SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict

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

class ConfigManager:
    """
    Singleton Configuration Manager with support for custom validators and secret masking.
    """

    _instance: Optional[ConfigManager] = None
    _initialized = False

    def __new__(cls) -> ConfigManager:
        if cls._instance is None:
            cls._instance = super(ConfigManager, cls).__new__(cls)
        return cls._instance

    def __init__(self) -> None:
        if self._initialized:
            return
        self._models: dict[str, type[BaseSettings]] = {}
        self._instances: dict[str, BaseSettings] = {}
        self._initialized = True

    def add_config(
        self,
        name: str,
        prefix: str,
        keys: list[str] | dict[str, type],
        defaults: Optional[dict[str, Any]] = None,
        validators: Optional[dict[str, Callable[[Any], Any]]] = None,
    ) -> None:
        """
        Register a new configuration namespace.
        """
        # Idempotency check
        if name in self._models:
            return

        defaults = defaults or {}
        validators = validators or {}

        # 1. Normalize input
        if isinstance(keys, list):
            field_definitions = {k.lower(): Any for k in keys}
        else:
            field_definitions = {k.lower(): t for k, t in keys.items()}

        model_fields = {}

        def _safe_int(v: Any) -> Optional[int]:
            if v is None:
                return None
            try:
                # Try simple conversion
                return int(v)
            except (ValueError, TypeError):
                # If failed, try stripping if string
                if isinstance(v, str):
                    v_str = v.strip()
                    if not v_str:
                        return None
                    # Try converting stripped string
                    try:
                        return int(v_str)
                    except ValueError:
                        # Log invalid value that isn't just empty
                        print(f"WARNING: ConfigManager received invalid int: {repr(v)}")
                        return None
                return None

        for key, field_type_in in field_definitions.items():
            field_type: Any = field_type_in
            # Apply robust int validation
            if field_type is int:
                field_type = Annotated[Optional[int], BeforeValidator(_safe_int)]

            # 2. Apply Dynamic Validator (if one exists for this key)
            if key in validators:
                validator_func = validators[key]
                field_type = Annotated[field_type, AfterValidator(validator_func)]

            # 3. Handle Defaults vs Required
            if key in defaults:
                model_fields[key] = (field_type, Field(default=defaults[key]))
            else:
                model_fields[key] = (field_type, Field(...))

        # 4. Create Model
        model_cls = create_model(
            name.capitalize() + "Settings",
            __base__=BaseSettings,
            **model_fields,
        )

        model_cls.model_config = SettingsConfigDict(
            env_prefix=f"{prefix}_",
            extra="ignore",
            case_sensitive=False,  # Ensure WH_TOKEN maps to wh_token
            env_ignore_empty=True,
        )

        # Rebuild is expensive, but necessary for Annotated fields
        model_cls.model_rebuild()

        self._models[name] = model_cls
        self._instances[name] = model_cls()

    def get_config(self, name: str) -> dict[str, Any]:
        """
        Returns the UNMASKED configuration dictionary for application use.
        Automatically converts SecretStr -> str.
        """
        inst = self._instances.get(name)
        if not inst:
            return {}

        # Optimization: model_dump with custom context or post-processing?
        # Actually, Pydantic 2 model_dump is fast. We just need to handle SecretStr efficiently.
        data = inst.model_dump()

        # Unwrap secrets so consumers get actual strings
        # We iteration over keys to avoid redundant checks if we knew which fields were secrets
        # But for dynamic models, we check types.
        for k, v in data.items():
            if isinstance(v, SecretStr):
                data[k] = v.get_secret_value()

        return data

    def get_masked_config(self, name: str) -> dict[str, Any]:
        """
        Returns a SAFE dictionary for logging/debugging.
        Secrets are replaced with '**********'.
        """
        inst = self._instances.get(name)
        if not inst:
            return {}

        data = inst.model_dump()

        # Convert SecretStr to their string representation (which is '**********')
        for k, v in data.items():
            if isinstance(v, SecretStr):
                data[k] = str(v)

        return data

    def get_json_schema(self, name: str) -> dict[str, Any]:
        cls = self._models.get(name)
        return cls.model_json_schema() if cls else {}

    def reload(self, name: Optional[str] = None) -> None:
        targets = [name] if name else list(self._models.keys())
        for target in targets:
            if cls := self._models.get(target):
                self._instances[target] = cls()

    @classmethod
    def reset(cls) -> None:
        cls._instance = None
        cls._initialized = False
