# Copyright 2018 Alan Rominger <arominge@redhat.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import annotations

import pytest

from ansible.inventory.group import Group
from ansible.inventory.host import Host
from ansible.errors import AnsibleError


def test_depth_update():
    A = Group('A')
    B = Group('B')
    Z = Group('Z')
    A.add_child_group(B)
    A.add_child_group(Z)
    assert A.depth == 0
    assert Z.depth == 1
    assert B.depth == 1


def test_depth_update_dual_branches():
    alpha = Group('alpha')
    A = Group('A')
    alpha.add_child_group(A)
    B = Group('B')
    A.add_child_group(B)
    Z = Group('Z')
    alpha.add_child_group(Z)
    beta = Group('beta')
    B.add_child_group(beta)
    Z.add_child_group(beta)

    assert alpha.depth == 0  # apex
    assert beta.depth == 3  # alpha -> A -> B -> beta

    omega = Group('omega')
    omega.add_child_group(alpha)

    # verify that both paths are traversed to get the max depth value
    assert B.depth == 3  # omega -> alpha -> A -> B
    assert beta.depth == 4  # B -> beta


def test_depth_recursion():
    A = Group('A')
    B = Group('B')
    A.add_child_group(B)
    # hypothetical of adding B as child group to A
    A.parent_groups.append(B)
    B.child_groups.append(A)
    # can't update depths of groups, because of loop
    with pytest.raises(AnsibleError):
        B._check_children_depth()


def test_loop_detection():
    A = Group('A')
    B = Group('B')
    C = Group('C')
    A.add_child_group(B)
    B.add_child_group(C)
    with pytest.raises(AnsibleError):
        C.add_child_group(A)


def test_direct_host_ordering():
    """Hosts are returned in order they are added
    """
    group = Group('A')
    # host names not added in alphabetical order
    host_name_list = ['z', 'b', 'c', 'a', 'p', 'q']
    expected_hosts = []
    for host_name in host_name_list:
        h = Host(host_name)
        group.add_host(h)
        expected_hosts.append(h)
    assert group.get_hosts() == expected_hosts


def test_sub_group_host_ordering():
    """With multiple nested groups, asserts that hosts are returned
    in deterministic order
    """
    top_group = Group('A')
    expected_hosts = []
    for name in ['z', 'b', 'c', 'a', 'p', 'q']:
        child = Group('group_{0}'.format(name))
        top_group.add_child_group(child)
        host = Host('host_{0}'.format(name))
        child.add_host(host)
        expected_hosts.append(host)
    assert top_group.get_hosts() == expected_hosts


def test_populates_descendant_hosts():
    A = Group('A')
    B = Group('B')
    C = Group('C')
    h = Host('h')
    C.add_host(h)
    A.add_child_group(B)  # B is child of A
    B.add_child_group(C)  # C is descendant of A
    A.add_child_group(B)
    assert set(h.groups) == set([C, B, A])
    h2 = Host('h2')
    C.add_host(h2)
    assert set(h2.groups) == set([C, B, A])


def test_ancestor_example():
    # see docstring for Group._walk_relationship
    groups = {}
    for name in ['A', 'B', 'C', 'D', 'E', 'F']:
        groups[name] = Group(name)
    # first row
    groups['A'].add_child_group(groups['D'])
    groups['B'].add_child_group(groups['D'])
    groups['B'].add_child_group(groups['E'])
    groups['C'].add_child_group(groups['D'])
    # second row
    groups['D'].add_child_group(groups['E'])
    groups['D'].add_child_group(groups['F'])
    groups['E'].add_child_group(groups['F'])

    assert (
        set(groups['F'].get_ancestors()) ==
        set([
            groups['A'], groups['B'], groups['C'], groups['D'], groups['E']
        ])
    )


def test_ancestors_recursive_loop_safe():
    """
    The get_ancestors method may be referenced before circular parenting
    checks, so the method is expected to be stable even with loops
    """
    A = Group('A')
    B = Group('B')
    A.parent_groups.append(B)
    B.parent_groups.append(A)
    # finishes in finite time
    assert A.get_ancestors() == set([A, B])


@pytest.mark.parametrize("priority, expected", [
    pytest.param(5, 5, id="int"),
    pytest.param('10', 10, id="string"),
    pytest.param(-1, -1, id="negative number"),
])
def test_set_priority_valid_values(priority, expected):
    """Test that valid priority value is set"""
    group = Group('test_group')
    group.set_priority(priority)
    assert group.priority == expected


@pytest.mark.parametrize("priority, expected", [
    pytest.param('invalid', 1, id="invalid string"),
    pytest.param(None, 1, id="None"),
    pytest.param({'key': 'value'}, 1, id="dict"),
    pytest.param(['item'], 1, id="list")
])
def test_set_priority_invalid_values(priority, expected):
    """Test that invalid priority value is handled gracefully with warnings"""
    group = Group('test_group')
    group.set_priority(priority)
    assert group.priority == expected
