import doctest
from collections import namedtuple

import pytest

import bc_jsonpath_ng
from bc_jsonpath_ng.ext import parse

Params = namedtuple("Params", "string initial_data insert_val target")


@pytest.mark.parametrize(
    "string, initial_data, insert_val, target",
    [
        Params(string="$.foo", initial_data={}, insert_val=42, target={"foo": 42}),
        Params(string="$.foo.bar", initial_data={}, insert_val=42, target={"foo": {"bar": 42}}),
        Params(string="$.foo[0]", initial_data={}, insert_val=42, target={"foo": [42]}),
        Params(string="$.foo[1]", initial_data={}, insert_val=42, target={"foo": [{}, 42]}),
        Params(string="$.foo[0].bar", initial_data={}, insert_val=42, target={"foo": [{"bar": 42}]}),
        Params(string="$.foo[1].bar", initial_data={}, insert_val=42, target={"foo": [{}, {"bar": 42}]}),
        Params(string="$.foo[0][0]", initial_data={}, insert_val=42, target={"foo": [[42]]}),
        Params(string="$.foo[1][1]", initial_data={}, insert_val=42, target={"foo": [{}, [{}, 42]]}),
        Params(string="foo[0]", initial_data={}, insert_val=42, target={"foo": [42]}),
        Params(string="foo[1]", initial_data={}, insert_val=42, target={"foo": [{}, 42]}),
        Params(string="foo", initial_data={}, insert_val=42, target={"foo": 42}),
        # Initial data can be a list if we expect a list back
        Params(string="[0]", initial_data=[], insert_val=42, target=[42]),
        Params(string="[1]", initial_data=[], insert_val=42, target=[{}, 42]),
        # Converts initial data to a list if necessary
        Params(string="[0]", initial_data={}, insert_val=42, target=[42]),
        Params(string="[1]", initial_data={}, insert_val=42, target=[{}, 42]),
        Params(
            string='foo[?bar="baz"].qux',
            initial_data={
                "foo": [
                    {"bar": "baz"},
                    {"bar": "bizzle"},
                ]
            },
            insert_val=42,
            target={"foo": [{"bar": "baz", "qux": 42}, {"bar": "bizzle"}]},
        ),
    ],
)
def test_update_or_create(string, initial_data, insert_val, target):
    jsonpath = parse(string)
    result = jsonpath.update_or_create(initial_data, insert_val)
    assert result == target


@pytest.mark.parametrize(
    "string, initial_data, insert_val, target",
    [
        # Slice not supported
        Params(string="foo[0:1]", initial_data={}, insert_val=42, target={"foo": [42, 42]}),
        # result is {'foo': {}}
        # Filter does not create items to meet criteria
        Params(
            string='foo[?bar="baz"].qux', initial_data={}, insert_val=42, target={"foo": [{"bar": "baz", "qux": 42}]}
        ),
        # result is {'foo': {}}
        # Does not convert initial data to a dictionary
        Params(string="foo", initial_data=[], insert_val=42, target={"foo": 42}),
        # raises TypeError
    ],
)
@pytest.mark.xfail
def test_unsupported_classes(string, initial_data, insert_val, target):
    jsonpath = parse(string)
    result = jsonpath.update_or_create(initial_data, insert_val)
    assert result == target


@pytest.mark.parametrize(
    "string, initial_data, insert_val, target",
    [
        Params(
            string="$.name[0].text",
            initial_data={},
            insert_val="Sir Michael",
            target={"name": [{"text": "Sir Michael"}]},
        ),
        Params(
            string="$.name[0].given[0]",
            initial_data={"name": [{"text": "Sir Michael"}]},
            insert_val="Michael",
            target={"name": [{"text": "Sir Michael", "given": ["Michael"]}]},
        ),
        Params(
            string="$.name[0].prefix[0]",
            initial_data={"name": [{"text": "Sir Michael", "given": ["Michael"]}]},
            insert_val="Sir",
            target={"name": [{"text": "Sir Michael", "given": ["Michael"], "prefix": ["Sir"]}]},
        ),
        Params(
            string="$.birthDate",
            initial_data={"name": [{"text": "Sir Michael", "given": ["Michael"], "prefix": ["Sir"]}]},
            insert_val="1943-05-05",
            target={
                "name": [{"text": "Sir Michael", "given": ["Michael"], "prefix": ["Sir"]}],
                "birthDate": "1943-05-05",
            },
        ),
    ],
)
def test_build_doc(string, initial_data, insert_val, target):
    jsonpath = parse(string)
    result = jsonpath.update_or_create(initial_data, insert_val)
    assert result == target


def test_doctests():
    results = doctest.testmod(bc_jsonpath_ng)
    assert results.failed == 0
