"""
Geracao dinamica de modelo de parametros em runtime.

Fontes de dados (prioridade):
1. Env var ONEROM_PARAMETERS (JSON dict injetado pelo runner)
2. Env var ONEROM_CONTEXT -> parameters (fallback)
3. Dict vazio

Validacao com schema (opcional, via ONEROM_PARAM_SCHEMA):
- Se presente: usa pydantic create_model para validacao tipada
- Se ausente: expoe como SimpleNamespace (acesso por atributo)

Tipos suportados (valores do backend ParameterType enum):
  string -> str
  int    -> int
  double -> float
  bool   -> bool

Aliases adicionais (compatibilidade com o prompt):
  integer -> int
  float   -> float
  json    -> dict
  list    -> list
"""
import json
import logging
import os
import types
import warnings
from typing import Any, Optional

logger = logging.getLogger(__name__)

TYPE_MAP: dict[str, type] = {
    "string": str,
    "int": int,
    "double": float,
    "bool": bool,
    # aliases
    "integer": int,
    "float": float,
    "json": dict,
    "list": list,
}


def _load_raw_parameters() -> dict:
    """Carrega parametros brutos das env vars injetadas pelo runner."""
    # Fonte 1: ONEROM_PARAMETERS (JSON string direto)
    raw = os.environ.get("ONEROM_PARAMETERS", "")
    if raw:
        try:
            data = json.loads(raw)
            if isinstance(data, dict):
                return data
        except (json.JSONDecodeError, TypeError):
            logger.warning("ONEROM_PARAMETERS nao e JSON valido, ignorando")

    # Fonte 2: ONEROM_CONTEXT -> parameters
    ctx_raw = os.environ.get("ONEROM_CONTEXT", "")
    if ctx_raw:
        try:
            ctx = json.loads(ctx_raw)
            params = ctx.get("parameters")
            if isinstance(params, dict):
                return params
        except (json.JSONDecodeError, TypeError):
            logger.warning("ONEROM_CONTEXT nao e JSON valido, ignorando")

    return {}


def _load_schema() -> list[dict] | None:
    """Carrega schema de parametros de ONEROM_PARAM_SCHEMA (opcional)."""
    raw = os.environ.get("ONEROM_PARAM_SCHEMA", "")
    if not raw:
        return None
    try:
        schema = json.loads(raw)
        if isinstance(schema, list):
            return schema
    except (json.JSONDecodeError, TypeError):
        logger.warning("ONEROM_PARAM_SCHEMA nao e JSON valido, ignorando schema")
    return None


def _coerce_value(value: Any, python_type: type) -> Any:
    """Converte value para python_type se possivel."""
    if value is None:
        return None
    if isinstance(value, python_type):
        return value
    # Conversoes especiais
    if python_type is bool:
        if isinstance(value, str):
            return value.lower() in ("true", "1", "yes")
        return bool(value)
    if python_type in (int, float):
        return python_type(value)
    if python_type is str:
        return str(value)
    # Para dict e list: tenta JSON parse se vier como string
    if python_type in (dict, list) and isinstance(value, str):
        try:
            parsed = json.loads(value)
            if isinstance(parsed, python_type):
                return parsed
        except (json.JSONDecodeError, TypeError):
            pass
    return value


def build_params() -> Any:
    """
    Constroi objeto de parametros validado.

    Com schema (ONEROM_PARAM_SCHEMA presente):
        Retorna instancia de Pydantic model dinamico com campos tipados.
        Levanta ValidationError se validacao falhar.

    Sem schema:
        Retorna SimpleNamespace com valores brutos (acesso por atributo).
        Tambem expoe .as_dict() para acesso como dict.
    """
    raw_params = _load_raw_parameters()
    schema = _load_schema()

    if schema:
        return _build_with_schema(raw_params, schema)
    else:
        return _build_simple(raw_params)


def _build_with_schema(raw_params: dict, schema: list[dict]) -> Any:
    """Gera Pydantic model dinamico a partir do schema."""
    try:
        from pydantic import create_model
        from pydantic import ValidationError
    except ImportError:
        warnings.warn(
            "pydantic nao instalado — parametros sem validacao de schema",
            RuntimeWarning,
            stacklevel=3,
        )
        return _build_simple(raw_params)

    fields: dict[str, Any] = {}
    coerced_values: dict[str, Any] = {}

    for item in schema:
        name = item.get("name")
        type_str = item.get("type", "string")
        required = item.get("required", False)
        default_value = item.get("default_value")  # string or None

        if not name:
            continue

        # Resolve tipo Python
        if type_str not in TYPE_MAP:
            logger.warning(
                f"Tipo desconhecido '{type_str}' para parametro '{name}', usando dict"
            )
            python_type = dict
        else:
            python_type = TYPE_MAP[type_str]

        # Prepara valor
        raw_val = raw_params.get(name)
        if raw_val is None and default_value is not None:
            raw_val = default_value

        coerced = _coerce_value(raw_val, python_type) if raw_val is not None else None
        coerced_values[name] = coerced

        # Define campo Pydantic
        if required:
            fields[name] = (python_type, ...)
        else:
            fields[name] = (Optional[python_type], None)

    # Campos extras em raw_params que nao estao no schema
    for key, val in raw_params.items():
        if key not in fields:
            fields[key] = (Any, None)
            coerced_values[key] = val

    if not fields:
        return _build_simple(raw_params)

    try:
        DynamicParams = create_model("ExecutionParams", **fields)
        # Filtra None para campos obrigatorios que tem valor
        return DynamicParams(**{k: v for k, v in coerced_values.items() if v is not None or k in coerced_values})
    except Exception as e:
        # Levanta erro claro — nunca silencia erros de validacao
        raise ValueError(
            f"Falha ao validar parametros da execucao: {e}\n"
            f"Parametros recebidos: {raw_params}"
        ) from e


def _build_simple(raw_params: dict) -> Any:
    """Retorna SimpleNamespace para acesso por atributo sem validacao de schema."""
    ns = types.SimpleNamespace(**raw_params)
    # Adiciona acesso como dict
    ns.as_dict = lambda: dict(raw_params)
    return ns
