# encoding: utf-8

from six import text_type

from ckan.lib.navl.dictization_functions import (
    flatten_schema,
    get_all_key_combinations,
    make_full_schema,
    flatten_dict,
    unflatten,
    missing,
    augment_data,
    validate,
    _validate,
)
from pprint import pformat
from ckan.lib.navl.validators import (
    identity_converter,
    empty,
    not_empty,
    ignore_missing,
    default,
    convert_int,
    ignore,
)


schema = {
    "__after": [identity_converter],
    "__extra": [identity_converter],
    "__junk": [identity_converter],
    "0": [identity_converter],
    "1": [identity_converter],
    "2": {
        "__before": [identity_converter],
        "__after": [identity_converter],
        "20": [identity_converter],
        "22": [identity_converter],
        "21": {"210": [identity_converter]},
    },
    "3": {"30": [identity_converter]},
}

data = {
    ("0",): "0 value",
    # key 1 missing
    ("2", 0, "20"): "20 value 0",
    # key 2,22 missing
    ("2", 0, "21", 0, "210"): "210 value 0,0",
    # key 3 missing subdict
    ("2", 1, "20"): "20 value 1",
    ("2", 1, "22"): "22 value 1",
    ("2", 1, "21", 0, "210"): "210 value 1,0",
    ("2", 1, "21", 1, "210"): "210 value 1,1",
    ("2", 1, "21", 3, "210"): "210 value 1,3",  ##out of order sequence
    ("4", 1, "30"): "30 value 1",  # junk key as no 4 and no subdict
    ("4",): "4 value",  # extra key 4
    #    ("2", 2, "21", 0, "210"): "210 value 2,0" #junk key as it does not have a parent
}


def test_flatten_schema():

    flattened_schema = flatten_schema(schema)

    assert flattened_schema == {
        ("0",): [identity_converter],
        ("1",): [identity_converter],
        ("2", "20"): [identity_converter],
        ("2", "__after"): [identity_converter],
        ("2", "__before"): [identity_converter],
        ("2", "21", "210"): [identity_converter],
        ("2", "22"): [identity_converter],
        ("3", "30"): [identity_converter],
        ("__after",): [identity_converter],
        ("__extra",): [identity_converter],
        ("__junk",): [identity_converter],
    }


def test_get_key_combination():

    flattened_schema = flatten_schema(schema)
    assert get_all_key_combinations(data, flattened_schema) == set(
        [
            (),
            ("2", 0),
            ("2", 1),
            ("2", 1, "21", 0),
            ("2", 0, "21", 0),
            ("2", 1, "21", 1),
            ("2", 1, "21", 3),
        ]
    ), get_all_key_combinations(data, flattened_schema)

    # state = {}
    # make_flattened_schema(data, schema, state)


def test_make_full_schema():

    full_schema = make_full_schema(data, schema)

    assert set(full_schema.keys()) - set(data.keys()) == set(
        [
            ("2", 1, "__before"),
            ("2", 0, "__after"),
            ("2", 0, "22"),
            ("1",),
            ("2", 1, "__after"),
            ("2", 0, "__before"),
            ("__after",),
            ("__extra",),
            ("__junk",),
        ]
    )

    assert set(data.keys()) - set(full_schema.keys()) == set(
        [("4",), ("4", 1, "30")]
    )


def test_augment_junk_and_extras():

    assert augment_data(data, schema) == {
        ("__junk",): {("4", 1, "30"): "30 value 1"},
        ("0",): "0 value",
        ("1",): missing,
        ("2", 0, "20"): "20 value 0",
        ("2", 0, "21", 0, "210"): "210 value 0,0",
        ("2", 0, "22"): missing,
        ("2", 1, "20"): "20 value 1",
        ("2", 1, "21", 0, "210"): "210 value 1,0",
        ("2", 1, "21", 1, "210"): "210 value 1,1",
        ("2", 1, "21", 3, "210"): "210 value 1,3",
        ("2", 1, "22"): "22 value 1",
        ("__extras",): {"4": "4 value"},
    }


def test_identity_validation():

    converted_data, errors = validate_flattened(data, schema)

    assert not errors

    assert sorted(converted_data) == sorted(
        {
            ("__junk",): {
                ("2", 2, "21", 0, "210"): "210 value 2,0",
                ("4", 1, "30"): "30 value 1",
            },
            ("0",): "0 value",
            ("1",): missing,
            ("2", 0, "20"): "20 value 0",
            ("2", 0, "21", 0, "210"): "210 value 0,0",
            ("2", 0, "22"): missing,
            ("2", 1, "20"): "20 value 1",
            ("2", 1, "21", 0, "210"): "210 value 1,0",
            ("2", 1, "21", 1, "210"): "210 value 1,1",
            ("2", 1, "21", 3, "210"): "210 value 1,3",
            ("2", 1, "22"): "22 value 1",
            ("__extras",): {"4": "4 value"},
        }
    ), pformat(sorted(converted_data))


def test_basic_errors():
    schema = {
        "__junk": [empty],
        "__extras": [empty],
        "0": [identity_converter],
        "1": [not_empty],
        "2": {
            "__before": [identity_converter],
            "__after": [identity_converter],
            "20": [identity_converter],
            "22": [identity_converter],
            "__extras": [empty],
            "21": {"210": [identity_converter]},
        },
        "3": {"30": [identity_converter]},
    }

    converted_data, errors = validate_flattened(data, schema)

    assert errors == {
        ("__junk",): [u"The input field [('4', 1, '30')] was not expected."],
        ("1",): [u"Missing value"],
        ("__extras",): [u"The input field __extras was not expected."],
    }, errors


def test_flatten():

    data = {
        "extras": [
            {"key": "genre", "value": u"horror"},
            {"key": "media", "value": u"dvd"},
        ],
        "license_id": u"gpl-3.0",
        "name": u"testpkg",
        "resources": [
            {
                u"alt_url": u"alt_url",
                u"description": u"Second file",
                u"extras": {u"size": u"200"},
                u"format": u"xml",
                u"hash": u"def123",
                u"url": u"http://blah.com/file2.xml",
            },
            {
                u"alt_url": u"alt_url",
                u"description": u"Main file",
                u"extras": {u"size": u"200"},
                u"format": u"xml",
                u"hash": u"abc123",
                u"url": u"http://blah.com/file.xml",
            },
        ],
        "tags": [{"name": u"russion"}, {"name": u"novel"}],
        "title": u"Some Title",
        "url": u"http://blahblahblah.mydomain",
    }

    assert flatten_dict(data) == {
        ("extras", 0, "key"): "genre",
        ("extras", 0, "value"): u"horror",
        ("extras", 1, "key"): "media",
        ("extras", 1, "value"): u"dvd",
        ("license_id",): u"gpl-3.0",
        ("name",): u"testpkg",
        ("resources", 0, u"alt_url"): u"alt_url",
        ("resources", 0, u"description"): u"Second file",
        ("resources", 0, u"extras"): {u"size": u"200"},
        ("resources", 0, u"format"): u"xml",
        ("resources", 0, u"hash"): u"def123",
        ("resources", 0, u"url"): u"http://blah.com/file2.xml",
        ("resources", 1, u"alt_url"): u"alt_url",
        ("resources", 1, u"description"): u"Main file",
        ("resources", 1, u"extras"): {u"size": u"200"},
        ("resources", 1, u"format"): u"xml",
        ("resources", 1, u"hash"): u"abc123",
        ("resources", 1, u"url"): u"http://blah.com/file.xml",
        ("tags", 0, "name"): u"russion",
        ("tags", 1, "name"): u"novel",
        ("title",): u"Some Title",
        ("url",): u"http://blahblahblah.mydomain",
    }, pformat(flatten_dict(data))

    assert data == unflatten(flatten_dict(data))


def test_flatten_deeper():
    data = {
        u"resources": [
            {
                u"subfields": [
                    {
                        u"test": u"hello",
                    },
                ],
            },
        ],
    }

    assert flatten_dict(data) == {
        ("resources", 0, u"subfields", 0, u"test"): u"hello",
    }, pformat(flatten_dict(data))

    assert data == unflatten(flatten_dict(data)), pformat(
        unflatten(flatten_dict(data)))


def test_unflatten_regression():
    fdata = {
        (u"items", 0, u"name"): u"first",
        (u"items", 0, u"value"): u"v1",
        (u"items", 3, u"name"): u"second",
        (u"items", 3, u"value"): u"v2",
    }
    expected = {
        u"items": [
            {u"name": u"first", u"value": u"v1"},
            {u"name": u"second", u"value": u"v2"},
        ],
    }
    assert unflatten(fdata) == expected, pformat(unflatten(fdata))


def test_simple():
    schema = {
        "name": [not_empty],
        "age": [ignore_missing, convert_int],
        "gender": [default("female")],
    }

    data = {"name": "fred", "age": "32"}

    converted_data, errors = validate(data, schema)

    assert not errors
    assert converted_data == {
        "gender": "female",
        "age": 32,
        "name": "fred",
    }, converted_data

    data = {"name": "", "age": "dsa32", "extra": "extra"}

    converted_data, errors = validate(data, schema)

    assert errors == {
        "age": [u"Please enter an integer value"],
        "name": [u"Missing value"],
    }, errors

    assert converted_data == {
        "gender": "female",
        "age": "dsa32",
        "name": "",
        "__extras": {"extra": "extra"},
    }

    data = {
        "name": "fred",
        "numbers": [
            {"number": "13221312"},
            {"number": "432423432", "code": "+44"},
        ],
    }

    schema = {
        "name": [not_empty],
        "numbers": {
            "number": [convert_int],
            "code": [not_empty],
            "__extras": [ignore],
        },
    }

    converted_data, errors = validate(data, schema)

    assert errors == {"numbers": [{"code": [u"Missing value"]}, {}]}


def test_error_list_position():
    data = {
        "name": "fred",
        "cats": [{"name": "rita"}, {"name": "otis"}],
        "numbers": [
            {"number": "432423432", "code": "+44"},
            {"number": "13221312"},
            {"number": "432423432", "code": "+44"},
            {"number": "13221312"},
            {"number": "432423432", "code": "+44"},
        ],
    }

    schema = {
        "name": [not_empty],
        "cats": {
            "name": [not_empty],
        },
        "numbers": {
            "number": [convert_int],
            "code": [not_empty],
            "__extras": [ignore],
        },
    }

    converted_data, errors = validate(data, schema)

    assert errors == {
            "numbers": [
                {},
                {"code": [u"Missing value"]},
                {},
                {"code": [u"Missing value"]},
                {},
            ]
    }


def test_simple_converter_types():
    schema = {
        "name": [not_empty, text_type],
        "age": [ignore_missing, int],
        "gender": [default("female")],
    }

    data = {"name": "fred", "age": "32"}

    converted_data, errors = validate(data, schema)
    assert not errors
    assert converted_data == {
        "gender": "female",
        "age": 32,
        "name": u"fred",
    }, converted_data

    assert isinstance(converted_data["name"], text_type)
    assert isinstance(converted_data["gender"], str)


def validate_flattened(data, schema, context=None):

    context = context or {}
    assert isinstance(data, dict)
    converted_data, errors = _validate(data, schema, context)

    for key, value in list(errors.items()):
        if not value:
            errors.pop(key)

    return converted_data, errors
