"""Auxiliary very lightweight pydantic-settings alternative"""

import os
from dataclasses import dataclass
from typing import Any, ClassVar, Literal, TypeVar, get_args, get_origin

T = TypeVar("T")

SupportedTypes = str | int | bool | float


def _str_to_bool(value: str) -> bool:
    """Convert string to boolean."""
    normalized = value.lower()
    if normalized in {"1", "true", "yes", "on"}:
        return True
    if normalized in {"0", "false", "no", "off"}:
        return False
    raise ValueError(f"Invalid bool value: '{value}'")


def _str_to_int(value: str) -> int:
    """Convert string to int."""
    try:
        return int(value)
    except ValueError:
        raise ValueError(f"Invalid int value: '{value}'") from None


def _str_to_float(value: str) -> float:
    """Convert string to float."""
    try:
        return float(value)
    except ValueError:
        return 0.0


def _str_to_literal(value: str, literal_values: tuple) -> Any:
    """Convert string to one of the allowed literal values."""
    for lit_val in literal_values:
        if isinstance(lit_val, str) and value == lit_val:
            return lit_val
        if isinstance(lit_val, bool) and _str_to_bool(value) == lit_val:
            return lit_val
        if isinstance(lit_val, int) and value.isdigit():
            if int(value) == lit_val:
                return lit_val
        if isinstance(lit_val, float):
            try:
                if float(value) == lit_val:
                    return lit_val
            except ValueError:
                pass
    raise ValueError(f"Invalid value '{value}' for Literal, expected one of {literal_values}")


@dataclass(frozen=True)
class AkaField:
    """Field descriptor with default value and alias support."""

    default: Any = None
    alias: str | None = None


class _FieldInfo:
    """Internal field metadata storage."""

    __slots__ = ("name", "type", "default", "alias", "has_alias", "is_literal", "literal_values")

    def __init__(self, name: str, type_: type, default: Any, alias: str | None = None):
        self.name = name
        self.type = type_
        self.default = default
        self.alias = alias
        self.has_alias = alias is not None
        self.is_literal = get_origin(type_) is Literal
        self.literal_values = get_args(type_) if self.is_literal else ()


class AkaBaseSettings:
    """Base settings class that reads from environment variables.

    Features:
    - Field aliases: e.g., debug with alias="d" can be set via DEBUG or D env var
    - Type coercion for str/int/bool/float/Literal
    - Default values
    - Ignores extra environment variables
    """

    _field_info: ClassVar[dict[str, _FieldInfo]] = {}

    def __str__(self) -> str:
        """Return a pretty string representation of the settings."""
        fields_str = ", ".join(f"{info.name}={repr(getattr(self, info.name))}" for info in self._field_info.values())
        return f"{self.__class__.__name__}({fields_str})"

    def __init_subclass__(cls, **kwargs: Any):
        """Parse class annotations and build field metadata."""
        super().__init_subclass__(**kwargs)

        fields = {}

        for name, type_ in cls.__annotations__.items():
            default_value = getattr(cls, name, None)
            alias = None

            if isinstance(default_value, AkaField):
                alias = default_value.alias
                default_value = default_value.default
            elif default_value is None:
                # For None defaults, check if it's Optional or required
                pass

            fields[name] = _FieldInfo(name, type_, default_value, alias)

        cls._field_info = fields

    def __init__(self, **kwargs: Any):
        """Initialize settings from environment variables and keyword arguments.

        Priority: environment variables > passed kwargs > default values.
        Extra kwargs that don't match any field are ignored.
        """
        self._set_values(**kwargs)

    def _set_values(self, **kwargs: Any) -> None:
        """Set field values from environment, kwargs, or defaults."""
        for info in self._field_info.values():
            value = self._get_value(info, **kwargs)
            object.__setattr__(self, info.name, value)

    def _get_value(self, info: _FieldInfo, **kwargs: Any) -> Any:
        """Get value for a field from env, kwargs, or default.

        Priority:
        1. Environment variable (alias first, then field name)
        2. Passed kwargs
        3. Default value
        """
        # Check environment by alias first, then by field name
        # Try original case first, then uppercase
        env_value = None

        if info.has_alias:
            env_value = os.environ.get(info.alias)
            if env_value is None:
                env_value = os.environ.get(info.alias.upper())

        if env_value is None:
            env_value = os.environ.get(info.name)
            if env_value is None:
                env_value = os.environ.get(info.name.upper())

        if env_value is not None:
            return self._convert_type(env_value, info.type, info.is_literal, info.literal_values)

        # Check kwargs by alias first, then by field name
        if info.has_alias and info.alias in kwargs:
            kwarg_value = kwargs[info.alias]
            return self._convert_type(str(kwarg_value), info.type, info.is_literal, info.literal_values)

        if info.name in kwargs:
            kwarg_value = kwargs[info.name]
            return self._convert_type(str(kwarg_value), info.type, info.is_literal, info.literal_values)

        return info.default

    def _convert_type(self, value: str, target_type: type, is_literal: bool, literal_values: tuple) -> Any:
        """Convert string value to target type."""

        if is_literal:
            return _str_to_literal(value, literal_values)

        if target_type is str:
            return value

        if target_type is int:
            return _str_to_int(value)

        if target_type is float:
            return _str_to_float(value)

        if target_type is bool:
            return _str_to_bool(value)

        return value
