# Copyright (c) 2020 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 collections import namedtuple

import pytest
from hypothesis import given, settings
from unittest.mock import ANY
from voluptuous import ALLOW_EXTRA, All, Any, Optional, Schema

from czc.schema_validators import UUID
from czc.unittest import MATCH, MockEx, schema_to_strategy


@pytest.fixture(scope='function')
def context(mocker):
    context = namedtuple('context', [])
    yield context
    mocker.stopall()


@pytest.mark.unit
@given(schema_to_strategy(UUID)())
@settings(max_examples=500)
def test_simple_schema_to_strategy(uuid):
    assert UUID(uuid)


complex_schema = Schema({
    'uuid': UUID,
    'list': [str],
    'int': int,
    'choice_key': Any(str, int),
    Optional('optional_key'): int,
    'nested': Schema({
        'nested_key': str,
    }),
}, required=True, extra=ALLOW_EXTRA)


@pytest.mark.unit
@given(schema_to_strategy(complex_schema)())
@settings(max_examples=500)
def test_complex_schema_to_strategy(data):
    assert complex_schema(data)


@pytest.mark.unit
@pytest.mark.parametrize('schema,exception', [
    (Schema(All(str)), AttributeError),
])
def test_schema_to_strategy_unsupported(schema, exception):
    with pytest.raises(exception):
        schema_to_strategy(schema).draw()


SAMPLE_ARGS_KWARGS = [
    ([], {}),
    (['arg'], {}),
    ([], {'kwarg': 'value'}),
    (['arg'], {'kwarg': 'value'}),
]


@pytest.mark.unit
@pytest.mark.parametrize('args,kwargs', SAMPLE_ARGS_KWARGS)
def test_mock_ex_if_called_with_anything(context, mocker, args, kwargs):
    mock = mocker.Mock()
    mock.mock_method(*args, 'extra arg', **kwargs, extra_kwarg=None)
    assert MockEx(mock.mock_method).if_called_with_anything()


@pytest.mark.unit
@pytest.mark.parametrize('args,kwargs', SAMPLE_ARGS_KWARGS)
def test_mock_ex_if_called_with_at_least(context, mocker, args, kwargs):
    mock = mocker.Mock()
    mock.mock_method(*args, 'extra arg', **kwargs, extra_kwarg=None)
    assert MockEx(mock.mock_method).if_called_with_at_least(*args, **kwargs)


@pytest.mark.unit
@pytest.mark.parametrize('args,kwargs', SAMPLE_ARGS_KWARGS)
def test_mock_ex_must_call_with(context, mocker, args, kwargs):
    mock = mocker.Mock()
    mock.mock_method('extra call')
    mock.mock_method(*args, **kwargs)
    assert MockEx(mock.mock_method).must_call_with(*args, **kwargs)


@pytest.mark.unit
@pytest.mark.parametrize('args,kwargs', SAMPLE_ARGS_KWARGS)
def test_mock_ex_was_called_once_with(context, mocker, args, kwargs):
    mock = mocker.Mock()
    mock.mock_method(*args, **kwargs)
    assert MockEx(mock.mock_method).was_called_once_with(*args, **kwargs)


@pytest.mark.unit
@pytest.mark.parametrize('args,kwargs', SAMPLE_ARGS_KWARGS)
def test_mock_ex_was_called_with(context, mocker, args, kwargs):
    mock = mocker.Mock()
    mock.mock_method(*args, **kwargs)
    mock.mock_method('extra call after')
    assert MockEx(mock.mock_method).was_called_with(*args, **kwargs)
    assert MockEx(mock.mock_method).was_called_with(*args, **kwargs) == True  # noqa: E712


@pytest.mark.unit
@pytest.mark.parametrize('args,kwargs', SAMPLE_ARGS_KWARGS)
def test_mock_ex_was_called_with_handles_ANY(context, mocker, args, kwargs):
    mock = mocker.Mock()
    mock.mock_method(*args, **kwargs)
    assert MockEx(mock.mock_method).was_called_with(*[ANY for _ in args], **{k: ANY for k, _ in kwargs.items()})


@pytest.mark.unit
@pytest.mark.parametrize('args,kwargs,check_args,check_kwargs', [
    (['a'], {}, [], {}),
    ([], {}, ['a'], {}),
    (['a'], {}, ['b'], {}),
    ([], {'a': 1}, [], {'a': 2}),
    ([], {'a': 1}, [], {'b': 1}),
    ([], {'a': 1}, [], {}),
    ([], {}, [], {'b': 1})
])
def test_mock_ex_was_called_with_mismatch(context, mocker,
                                          args, kwargs,
                                          check_args, check_kwargs):
    mock = mocker.Mock()
    mock.mock_method(*args, **kwargs)
    assert not MockEx(mock.mock_method).was_called_with(*check_args, **check_kwargs)
    assert MockEx(mock.mock_method).was_called_with(*check_args, **check_kwargs) == False  # noqa: E712


@pytest.mark.unit
@pytest.mark.parametrize('func,other,expected', [
    (lambda x: x == 1, 1, True),
    (lambda x: x == 1, 2, False),
])
def test_match(func, other, expected):
    pos = MATCH(func) == other
    neg = MATCH(func) != other
    assert pos is expected
    assert neg is not expected
