"""Create dataclasses from dictionaries"""

from dataclasses import is_dataclass, fields, MISSING
from enum import Enum
from types import NoneType, UnionType
from typing import get_origin, get_args, Literal, Any
from dcjdict.encode import JsonableDict

_dataclasses = {}  # Dictionary of dataclasses accesible by string

_JSON_BASIC = (str, int, float, bool, None, NoneType)


def from_jdict(value: dict, to_cls: type, validate_types: bool = True) -> object:
    value = JsonableDict(value)
    if not is_dataclass(to_cls):
        raise TypeError(f"Type {to_cls} is not a dataclass")

    _dataclasses[to_cls.__name__] = to_cls

    init_values = {}
    for field in fields(to_cls):
        if field.name in value:
            init_values[field.name] = _decode_value(
                value[field.name], field.type, validate_types
            )
        else:
            if field.default is MISSING and field.default_factory is MISSING:
                raise ValueError(
                    f"Field {field.name} is missing and has no default value."
                )

    return to_cls(**init_values)


def _decode_value(value, field_typing, validate_types):
    origin = get_origin(field_typing)
    if origin is None:  # It is a class
        print(f"Entering {field_typing}")
        if is_dataclass(field_typing) and isinstance(field_typing, type):
            _dataclasses[field_typing.__name__] = field_typing
            print(f"Found dataclass {field_typing} for value {value}")
            dc = from_jdict(value, field_typing, validate_types)

            print(f"Decoded {value} to {dc}")
            return dc

        if type(field_typing) is str:
            print(f"Finding {field_typing} in {_dataclasses}")

            if field_typing not in _dataclasses:
                raise TypeError(f'Type "{field_typing}" not found to be decoded')

            dc = from_jdict(value, _dataclasses[field_typing], validate_types)
            print(f"Decoded {value} to {dc}")
            return dc

        if not isinstance(field_typing, type):
            raise TypeError(f"Field typing {field_typing} is not a type")

        if issubclass(field_typing, Enum):
            return field_typing[value] if type(value) is str else field_typing(value)

        if field_typing not in _JSON_BASIC:
            raise TypeError(
                f"Value {value} of type {field_typing} is not a jsonable object"
            )

        return value

    args = get_args(field_typing)

    if origin is list:
        return [_decode_value(item, args[0], validate_types) for item in value]

    if origin is dict:
        return {
            key: _decode_value(val, args[1], validate_types)
            for key, val in value.items()
        }

    if origin is tuple:
        if len(args) == 2 and args[1] == ...:
            return tuple(
                [_decode_value(item, args[0], validate_types) for item in value]
            )
        else:
            return tuple(
                [
                    _decode_value(item, _typing, validate_types)
                    for _typing, item in zip(args, value)
                ]
            )

    if origin is dict and args[1] != Any:
        return {
            key: _decode_value(value_, args[1], validate_types)
            for key, value_ in value.items()
        }

    if origin is UnionType:
        print("estoy aquí!!!")
        for type_ in args:
            try:
                return _decode_value(value, type_, validate_types)
            except Exception as ex:
                print(ex)
                ...

    if origin is Literal:
        for type_ in args:
            if value == type_:
                return value
        raise ValueError(f"Value {value} is not a valid argument for Literal")

    if (field_typing is NoneType or field_typing is None) and value is None:
        return

    raise TypeError(f"Not possible to construct {value} from {field_typing}")


def _validate_types(value, typing): ...
