import inspect
from typing import Any, Literal, Union

JSON_TYPE_TO_PYTHON_TYPE = {
    "string": str,
    "number": float,
    "boolean": bool,
    "integer": int,
    "object": dict,
    "array": list,
    "null": type(None),
}


def _dedupe_types(types: list[Any]) -> list[Any]:
    seen = []
    for t in types:
        if t not in seen:
            seen.append(t)
    return seen


def _annotation_from_spec(spec: dict[str, Any]) -> Any:
    if not spec or not isinstance(spec, dict):
        return Any

    # const → Literal[value]
    if "const" in spec:
        return Literal[spec["const"]]

    # enum → Literal[val1, val2, ...]
    if "enum" in spec:
        values = spec["enum"]
        if not values:
            return Any
        return Literal[tuple(values)]

    # anyOf / oneOf → Union[Type1, Type2, ...]
    for key in ("anyOf", "oneOf"):
        if key in spec:
            variants = [_annotation_from_spec(s) for s in spec[key]]
            unique = _dedupe_types(variants)
            if len(unique) == 0:
                return Any
            if len(unique) == 1:
                return unique[0]
            return Union[tuple(unique)]

    # allOf → merge sub-schemas then resolve
    if "allOf" in spec:
        merged = {}
        for sub in spec["allOf"]:
            merged.update(sub)
        return _annotation_from_spec(merged)

    json_type = spec.get("type")

    if json_type is None:
        return Any

    # type as list, e.g. ["string", "null"] → Union[str, None] (Optional)
    if isinstance(json_type, list):
        types = [JSON_TYPE_TO_PYTHON_TYPE.get(t, Any) for t in json_type]
        unique = _dedupe_types(types)
        if len(unique) == 0:
            return Any
        if len(unique) == 1:
            return unique[0]
        return Union[tuple(unique)]

    # array with items → list[ItemType]
    if json_type == "array" and "items" in spec:
        item_type = _annotation_from_spec(spec["items"])
        return list[item_type]  # type: ignore[valid-type]

    return JSON_TYPE_TO_PYTHON_TYPE.get(json_type, Any)


def signature_from_schema(schema: object | None) -> inspect.Signature:
    if not schema or not isinstance(schema, dict):
        return inspect.Signature()

    props = schema.get("properties", {})
    required = set(schema.get("required", []))
    params = []

    for name, spec in props.items():
        annotation = _annotation_from_spec(spec)

        has_schema_default = "default" in spec if isinstance(spec, dict) else False

        if name in required and not has_schema_default:
            default = inspect.Parameter.empty
        elif has_schema_default:
            default = spec["default"]
        else:
            default = None

        params.append(
            inspect.Parameter(
                name,
                inspect.Parameter.POSITIONAL_OR_KEYWORD,
                default=default,
                annotation=annotation,
            )
        )

    return inspect.Signature(params)
