# -*- coding: utf-8 -*-
# Copyright (c) 2017 CloudZero - ALL RIGHTS RESERVED - PROPRIETARY AND CONFIDENTIAL
# Unauthorized copying of this file and/or project, via any medium is strictly prohibited.
# Direct all questions to legal@cloudzero.com

from functools import reduce
from random import randint
from typing import Callable

import hypothesis.strategies as st
from voluptuous import Any, Coerce, Match, Number, Optional, Required, Schema


# Voluptuous Hypothesis
def _from_schema(draw, s):
    if isinstance(s, dict):
        return reduce(lambda acc, i: {**acc, _from_schema(draw, i[0]): _from_schema(draw, i[1])}, s.items(), {})
    if isinstance(s, list):
        return reduce(lambda acc, v: acc + [_from_schema(draw, v), ], s, [])
    if isinstance(s, set):
        return reduce(lambda acc, v: acc | {_from_schema(draw, v), }, s, set())
    if isinstance(s, tuple):
        return reduce(lambda acc, v: acc + (_from_schema(draw, v), ), s, ())
    if isinstance(s, Schema):
        return _from_schema(draw, s.__dict__['schema'])
    if isinstance(s, Required):
        return s.schema
    if isinstance(s, Optional):
        return s.schema
    if isinstance(s, Any):
        return _from_schema(draw, s.validators[randint(0, len(s.validators))])
    if isinstance(s, Coerce):
        return _from_schema(draw, s.type)
    if isinstance(s, Number):
        kwargs = {'allow_nan': False, 'allow_infinity': False}
        return draw(st.decimals(**kwargs)) if s.yield_decimal else draw(st.floats(**kwargs))
    if isinstance(s, Match):
        return draw(st.from_regex(s.pattern, fullmatch=True))
    if callable(s) and s.__name__ == 'Email':
        user = 'foo'
        domain = 'bar'
        return f'{user}@{domain}.com'
    if isinstance(s, str):
        return s
    if s is None:
        return None
    return draw(st.from_type(s))


def schema_to_strategy(schema: Schema) -> Callable:
    @st.composite
    def f(draw):
        return _from_schema(draw, schema.__dict__['schema'])
    return f
