from typing import Any, Type, TypeVar
from pydantic import BaseModel, Field
from datetime import datetime, timezone

SchemaT = TypeVar("SchemaT", bound=BaseModel)


class Error(BaseModel):
    code: str = Field(
        ...,
        description=(
            "Código de erro categórico (ex.: VALIDATION_ERROR, AUTH_ERROR)"
        ),
    )
    message: str = Field(..., description="Mensagem resumida do erro")
    details: Any | None = Field(
        None, description="Detalhes adicionais do erro")


class ErrorResponse(BaseModel):
    error: Error = Field(..., description="Objeto com informações do erro")
    requestId: str | None = Field(
        None, description="Identificador da requisição para rastreio")
    timestamp: str = Field(
        ..., description="Timestamp ISO8601 da ocorrência do erro"
    )


class SuccessResponse(BaseModel):
    message: str | None = Field(
        None, description="Mensagem amigável sobre o resultado")
    data: Any | None = Field(
        None, description="Carga útil com o resultado da operação")
    requestId: str | None = Field(
        None, description="Identificador da requisição para rastreio")
    timestamp: str = Field(
        ..., description="Timestamp ISO8601 do processamento"
    )


def build_success_payload(
    data: Any | None = None,
    *,
    message: str | None = None,
    request_id: str | None = None,
    timestamp_iso: str | None = None
) -> dict[str, Any]:
    """
    Constrói um payload de sucesso padronizado para respostas de API.
    - `data`: carga útil da resposta.
    - `message`: mensagem amigável.
    - `request_id`: identificador da requisição.
    - `timestamp_iso`: timestamp opcional (ISO8601).
    """
    ts = timestamp_iso or datetime.now(timezone.utc).isoformat()
    return SuccessResponse(
        message=message,
        data=data,
        requestId=request_id,
        timestamp=ts
    ).model_dump(by_alias=True, exclude_unset=True)


def build_error_payload(
    code: str | None = None,
    message: str | None = None,
    details: Any | None = None,
    *,
    error_code: str | None = None,
    error_message: str | None = None,
    request_id: str | None = None,
    timestamp_iso: str | None = None
) -> dict[str, Any]:
    """
    Constrói um payload de erro padronizado para respostas de API.
    - `code`/`error_code`: código do erro (ex.: VALIDATION_ERROR)
    - `message`/`error_message`: mensagem resumida
    - `details`: detalhes adicionais
    - `request_id`: identificador da requisição
    - `timestamp_iso`: timestamp opcional (ISO8601)
    """
    resolved_code = code or error_code
    resolved_message = message or error_message
    if not resolved_code or not resolved_message:
        raise ValueError("code/message are required for error payload")

    ts = timestamp_iso or datetime.now(timezone.utc).isoformat()
    error = Error(
        code=resolved_code,
        message=resolved_message,
        details=details
    )
    return ErrorResponse(
        error=error,
        requestId=request_id,
        timestamp=ts
    ).model_dump(by_alias=True, exclude_unset=True)


def to_schema(schema_cls: Type[SchemaT], obj: Any) -> SchemaT:
    """
    Converte um objeto ORM ou dict para o schema Pydantic correspondente.
    Funciona para:
        - dict
        - ORM com from_attributes=True
        - objetos normais (usa __dict__)
    """
    if isinstance(obj, dict):
        return schema_cls(**obj)

    model_validate = getattr(schema_cls, "model_validate", None)
    if callable(model_validate):
        try:
            return model_validate(obj)
        except Exception:
            pass

    from_orm = getattr(schema_cls, "from_orm", None)
    if callable(from_orm):
        try:
            return from_orm(obj)
        except Exception:
            pass

    data = getattr(obj, "__dict__", None)
    if isinstance(data, dict):
        cleaned = {k: v for k, v in data.items() if not k.startswith("_sa_")}
        return schema_cls(**cleaned)

    return schema_cls(**(obj or {}))


def to_schema_dict(schema_cls: Type[SchemaT], obj: Any) -> dict[str, Any]:
    """
    Converte um objeto para schema Pydantic e retorna como dict.
    Usa `by_alias=True` e `exclude_unset=True` para manter os aliases corretos.
    """
    instance = to_schema(schema_cls, obj)
    return instance.model_dump(by_alias=True, exclude_unset=True)
