import pytest
import mock
from mock import Mock

from awx.main.models import (
    Job,
    InstanceGroup,
)


def T(impact):
    j = mock.Mock(Job())
    j.task_impact = impact
    return j


def Is(param):
    '''
    param:
        [remaining_capacity1, remaining_capacity2, remaining_capacity3, ...]
        [(jobs_running1, capacity1), (jobs_running2, capacity2), (jobs_running3, capacity3), ...]
    '''

    instances = []
    if isinstance(param[0], tuple):
        for (jobs_running, capacity) in param:
            inst = Mock()
            inst.capacity = capacity
            inst.jobs_running = jobs_running
            instances.append(inst)
    else:
        for i in param:
            inst = Mock()
            inst.remaining_capacity = i
            instances.append(inst)
    return instances


class TestInstanceGroup(object):
    @pytest.mark.parametrize('task,instances,instance_fit_index,reason', [
        (T(100), Is([100]), 0, "Only one, pick it"),
        (T(100), Is([100, 100]), 0, "Two equally good fits, pick the first"),
        (T(100), Is([50, 100]), 1, "First instance not as good as second instance"),
        (T(100), Is([50, 0, 20, 100, 100, 100, 30, 20]), 3, "Pick Instance [3] as it is the first that the task fits in."),
        (T(100), Is([50, 0, 20, 99, 11, 1, 5, 99]), None, "The task don't a fit, you must a quit!"),
    ])
    def test_fit_task_to_most_remaining_capacity_instance(self, task, instances, instance_fit_index, reason):
        with mock.patch.object(InstanceGroup,
                               'instances',
                               Mock(spec_set=['filter'],
                                    filter=lambda *args, **kargs: Mock(spec_set=['order_by'],
                                                                       order_by=lambda x: instances))):
            ig = InstanceGroup(id=10)

            if instance_fit_index is None:
                assert ig.fit_task_to_most_remaining_capacity_instance(task) is None, reason
            else:
                assert ig.fit_task_to_most_remaining_capacity_instance(task) == \
                    instances[instance_fit_index], reason


    @pytest.mark.parametrize('instances,instance_fit_index,reason', [
        (Is([(0, 100)]), 0, "One idle instance, pick it"),
        (Is([(1, 100)]), None, "One un-idle instance, pick nothing"),
        (Is([(0, 100), (0, 200), (1, 500), (0, 700)]), 3, "Pick the largest idle instance"),
        (Is([(0, 100), (0, 200), (1, 10000), (0, 700), (0, 699)]), 3, "Pick the largest idle instance"),
        (Is([(0, 0)]), None, "One idle but down instance, don't pick it"),
    ])
    def test_find_largest_idle_instance(self, instances, instance_fit_index, reason):
        def filter_offline_instances(*args):
            return filter(lambda i: i.capacity > 0, instances)

        with mock.patch.object(InstanceGroup,
                               'instances',
                               Mock(spec_set=['filter'],
                                    filter=lambda *args, **kargs: Mock(spec_set=['order_by'],
                                                                       order_by=filter_offline_instances))):
            ig = InstanceGroup(id=10)

            if instance_fit_index is None:
                assert ig.find_largest_idle_instance() is None, reason
            else:
                assert ig.find_largest_idle_instance() == \
                    instances[instance_fit_index], reason

