"""config_from_env — popula dataclasses desde variables de entorno usando field metadata."""

import os
from dataclasses import fields as dc_fields


def config_from_env(target_class: type) -> object:
    """Crea una instancia de un dataclass poblada desde variables de entorno.

    Cada campo del dataclass puede tener metadata que guia el mapeo:
      - ``env``: nombre de la variable de entorno (obligatorio para usar este sistema)
      - ``required``: bool, si True y la var no esta seteada lanza ValueError
      - ``default``: valor string usado cuando la var no esta seteada (opcional)
      - ``secret``: bool, documentacion solamente, sin efecto en runtime

    La conversion de tipo es automatica segun el tipo anotado del campo:
      - ``str``: sin conversion
      - ``int``: int(value)
      - ``float``: float(value)
      - ``bool``: True para "1", "true", "yes" (case-insensitive); False para el resto
      - ``list[str]`` o ``list``: split por coma, stripping espacios

    Args:
        target_class: clase dataclass con campo metadata en sus fields.

    Returns:
        instancia de target_class con todos los campos poblados.

    Raises:
        ValueError: si un campo requerido no esta seteado, o si la conversion de tipo falla.
        TypeError: si target_class no es un dataclass.
    """
    try:
        all_fields = dc_fields(target_class)  # raises TypeError if not dataclass
    except TypeError:
        raise TypeError(f"config_from_env: {target_class.__name__} is not a dataclass")

    kwargs: dict = {}
    errors: list[str] = []

    for f in all_fields:
        meta = f.metadata
        env_key: str | None = meta.get("env")
        if env_key is None:
            # no env tag → leave at dataclass default (will be provided via default_factory)
            continue

        required: bool = meta.get("required", False)
        default_str: str | None = meta.get("default")

        raw = os.environ.get(env_key, "")
        if not raw:
            if default_str is not None:
                raw = default_str
            elif required:
                errors.append(f"field '{f.name}': env var '{env_key}' is required but not set")
                continue
            else:
                continue  # optional, unset — use dataclass default

        # type conversion
        try:
            kwargs[f.name] = _coerce(raw, f.type, f.name)
        except (ValueError, TypeError) as exc:
            errors.append(str(exc))

    if errors:
        raise ValueError("config_from_env: " + "; ".join(errors))

    return target_class(**kwargs)


def _coerce(raw: str, annotation: type | str, field_name: str) -> object:
    """Coerce raw string to the annotated type."""
    # handle string annotations (from __future__ import annotations or forward refs)
    if isinstance(annotation, str):
        ann_str = annotation.lower().strip()
    else:
        # Get the type name for comparison
        ann_str = getattr(annotation, "__name__", str(annotation)).lower()

    if ann_str in ("str", "string"):
        return raw

    if ann_str == "int":
        try:
            return int(raw)
        except ValueError:
            raise ValueError(f"field '{field_name}': cannot convert {raw!r} to int")

    if ann_str == "float":
        try:
            return float(raw)
        except ValueError:
            raise ValueError(f"field '{field_name}': cannot convert {raw!r} to float")

    if ann_str == "bool":
        return raw.strip().lower() in ("1", "true", "yes")

    # list / list[str]
    if "list" in ann_str:
        parts = [p.strip() for p in raw.split(",") if p.strip()]
        return parts

    # fallback: return as string
    return raw
