
import pytest

import inspect
from introspection import Signature, BoundArguments


def test_bound_args_from_bound_arguments():
    sig = inspect.signature(lambda a, b, *c: None)
    bound_args = sig.bind('A', 'B', 'C', 'D')

    bound_args = BoundArguments.from_bound_arguments(bound_args)

    assert bound_args.arguments == {'a': 'A', 'b': 'B', 'c': ('C', 'D')}


@pytest.mark.parametrize('func, args, kwargs, expected_args, expected_kwargs', [
    ((lambda a, b, c:0), [9, 8, 7], {}, [9, 8, 7], {}),
    ((lambda a, b, c:0), [9, 8], {}, [9, 8], {}),
    ((lambda *b, c:0), [9, 8], {'c': 7}, [9, 8], {'c': 7}),
    (range, [9, 8, 7], {}, [9, 8, 7], {}),
    ((lambda a, *b, c:0), [9], {'c': 7}, [9], {'c': 7}),
    ((lambda a, *b, **c:0), [9, 8, 7], {'foo': 'bar'}, [9, 8, 7], {'foo': 'bar'}),
])
def test_to_varargs_prefer_args(func, args, kwargs, expected_args, expected_kwargs):
    sig = Signature.from_callable(func)
    bound_args = sig.bind_partial(*args, **kwargs)

    args, kwargs = bound_args.to_varargs(prefer='args')

    assert args == expected_args
    assert kwargs == expected_kwargs


@pytest.mark.parametrize('func, args, kwargs, expected_args, expected_kwargs', [
    ((lambda a, b, c:0), [9, 8, 7], {}, [], {'a': 9, 'b': 8, 'c': 7}),
    ((lambda a, b, c:0), [9, 8], {}, [], {'a': 9, 'b': 8}),
    ((lambda *b, c:0), [9, 8], {'c': 7}, [9, 8], {'c': 7}),
    (range, [9, 8, 7], {}, [9, 8, 7], {}),
    ((lambda a, *b, c:0), [9], {'c': 7}, [], {'a': 9, 'c': 7}),
    ((lambda a, *b, **c:0), [9, 8, 7], {'foo': 'bar'}, [9, 8, 7], {'foo': 'bar'}),
])
def test_to_varargs_prefer_kwargs(func, args, kwargs, expected_args, expected_kwargs):
    sig = Signature.from_callable(func)
    bound_args = sig.bind_partial(*args, **kwargs)

    args, kwargs = bound_args.to_varargs(prefer='kwargs')

    assert args == expected_args
    assert kwargs == expected_kwargs


def test_to_varargs_invalid_prefer_arg():
    bound_args = Signature.from_callable(lambda x: x).bind(3)

    with pytest.raises(ValueError):
        bound_args.to_varargs(prefer='foobar')


def test_iter():
    bound_args = Signature.from_callable(lambda x, y, z: x).bind_partial(3, 4)

    assert tuple(bound_args) == ('x', 'y')


def test_len():
    bound_args = Signature.from_callable(lambda x, y: x).bind_partial(3)

    assert len(bound_args) == 1


def test_getitem():
    bound_args = Signature.from_callable(lambda x: x).bind(3)

    assert bound_args['x'] == 3


def test_getitem_keyerror():
    bound_args = Signature.from_callable(lambda x: x).bind(3)

    with pytest.raises(KeyError):
        bound_args['y']


def test_setitem():
    bound_args = Signature.from_callable(lambda x: x).bind(3)

    bound_args['x'] = 17
    assert bound_args['x'] == 17


@pytest.mark.parametrize('func, args, kwargs, expected', [
    (lambda x: 0, [], {}, {'x'}),
    (lambda x=3: 0, [], {}, set()),
    (lambda x: 0, [3], {}, set()),
])
def test_get_missing_parameter_names(func, args, kwargs, expected):
    bound_args = Signature.from_callable(func).bind_partial(*args, **kwargs)

    assert bound_args.get_missing_parameter_names() == expected


@pytest.mark.parametrize('func, args, kwargs, expected', [
    (lambda x=3: 0, [], {}, {'x'}),
    (lambda *args: 0, [], {}, set()),
    (lambda **kwargs: 0, [], {}, set()),
])
def test_get_missing_parameter_names_with_optionals(func, args, kwargs, expected):
    bound_args = Signature.from_callable(func).bind_partial(*args, **kwargs)

    result = bound_args.get_missing_parameter_names(include_optional_parameters=True)
    assert result == expected
