# -*- coding: utf-8 -*-

from __future__ import (division, absolute_import, print_function,
                        unicode_literals)
from kubedifflib._kube import KubeObject
from kubedifflib._diff import diff_lists, list_subtract, Difference
from hypothesis.strategies import integers, lists, text, fixed_dictionaries, sampled_from, none, one_of
from hypothesis import given, example
import random
import copy


@given(path=text(), xs=lists(integers()))
def test_diff_lists_equal(path, xs):
    """No difference between a list and itself."""
    assert list(diff_lists(path, xs, xs)) == []


@given(path=text(), xs=lists(integers()))
def test_same_list_shuffled_is_not_different(path, xs):
    """No difference between two lists with same values in different order."""
    ys = list(xs)
    random.shuffle(ys)
    assert list(diff_lists(path, xs, ys)) == []


@given(path=text(), xs=lists(lists(integers())))
def test_same_list_shuffled_is_not_different_nested(path, xs):
    """No difference between two lists with same values in different order.

    This variant is for when there are nested lists, to catch loop / mutation
    issues.
    """
    ys = copy.deepcopy(xs)
    random.shuffle(ys)
    assert list(diff_lists(path, xs, ys)) == []


@given(path=text(), base=lists(integers()), extension=lists(integers()))
def test_added_items_appear_in_diff(path, base, extension):
    xs = list(base)
    xs.extend([None] * len(extension))
    ys = list(base)
    ys.extend(extension)
    assert len(list(diff_lists(path, xs, ys))) == len(extension)


@given(xs=lists(integers()))
def test_list_subtract_same_list(xs):
    assert list(list_subtract(xs, xs)) == []
    ys = list(xs)
    random.shuffle(ys)
    assert list(list_subtract(xs, ys)) == []


@given(xs=lists(integers()), ys=lists(integers()))
def test_list_subtract_recover(xs, ys):
    missing = list_subtract(xs, ys)
    zs = list(ys)
    zs.extend([xs[i] for i in missing])
    assert list(list_subtract(xs, zs)) == []


def two_lists_of_same_size(generator):
    """Generate two lists of the same length."""
    return lists(generator).map(split_list)


def split_list(xs):
    """Split a list into two lists of equal length."""
    midpoint, remainder = divmod(len(xs), 2)
    if remainder:
        xs = xs[:-1]
    return xs[:midpoint], xs[midpoint:]


@given(path=text(), items=two_lists_of_same_size(integers()))
def test_diff_lists_doesnt_mutate_inputs(path, items):
    xs, ys = items
    orig_xs = list(xs)
    orig_ys = list(ys)
    diff_lists(path, xs, ys)
    assert xs == orig_xs and ys == orig_ys


@given(path=text(), items=two_lists_of_same_size(lists(integers())))
def test_diff_lists_doesnt_mutate_inputs_nested_lists(path, items):
    xs, ys = items
    orig_xs = copy.deepcopy(xs)
    orig_ys = copy.deepcopy(ys)
    diff_lists(path, xs, ys)
    assert xs == orig_xs and ys == orig_ys


@given(items=two_lists_of_same_size(integers()))
def test_two_lists_of_same_size_generator(items):
    """two_lists_of_same_size returns two lists of the same length."""
    xs, ys = items
    assert len(xs) == len(ys)


def kube_spec():
    """Generate a random kubernetes spec."""
    return fixed_dictionaries({
        "name": text()
    })


def kube_obj():
    """Generate a random Kubernetes object."""
    return fixed_dictionaries({
        "apiVersion": sampled_from(["v1", "v1beta1", "extensions/v1"]),
        "kind": sampled_from(["Namespace", "Pod", "Job", "CronJob", "Deployment"]),
        "metadata": fixed_dictionaries({
            "name": text()
        }),
        "spec": one_of(none(), kube_spec())
    })


def kube_list():
    """Generate a Kubernetes List type with a set of objects."""
    return fixed_dictionaries({
        "apiVersion": sampled_from(["v1", "v1beta1"]),
        "kind": sampled_from(["List"]),
        "items": lists(kube_obj(), min_size=1)
    })


@given(data=kube_list())
def test_from_dict_kubernetes_list_type(data):
    """KubeObject.from_dict parses kubernetes lists by returning each of the
    items in the list."""
    assert [kube_obj.data for kube_obj in KubeObject.from_dict(data)] == data['items']


@given(data=kube_obj())
def test_from_dict_kubernetes_obj_type(data):
    """KubeObject.from_dict parses regular kubernetes objects."""
    assert [kube_obj.data for kube_obj in KubeObject.from_dict(data)] == [data]


@given(path=text(), kind=text())
def test_difference_no_args(path, kind):
    """Difference.to_text works as expected when no args passed."""
    d = Difference("Message", path)
    assert d.to_text(kind) == path + ": Message"


@given(path=text(), kind=text(), arg1=text(), arg2=one_of(text(), none()))
@example("somepath", "Secret", "foo", None)
def test_difference_two_args(path, kind, arg1, arg2):
    """Difference.to_text works when two args passed, that may be 'none'."""
    d = Difference("Message %s %s", path, arg1, arg2)
    assert d.to_text(kind) != ""
