from unittest.mock import MagicMock

import pytest

from garcon import activity
from garcon import runner
from garcon import task


EMPTY_CONTEXT = dict()


def test_execute_default_task_runner():
    """Should throw an exception.
    """

    current_runner = runner.BaseRunner()
    with pytest.raises(NotImplementedError):
        current_runner.execute(None, None)


def test_synchronous_tasks(monkeypatch, boto_client):
    """Test synchronous tasks.
    """

    monkeypatch.setattr(activity.ActivityExecution, 'heartbeat',
        lambda self: None)

    resp = dict(foo='bar')
    current_runner = runner.Sync(
        MagicMock(), MagicMock(return_value=resp))
    current_activity = activity.ActivityExecution(
        boto_client, 'activityId', 'taskToken', '{"context": "value"}')

    result = current_runner.execute(current_activity, EMPTY_CONTEXT)

    assert len(current_runner.tasks) == 2

    for current_task in task.flatten(current_runner.tasks, EMPTY_CONTEXT):
        assert current_task.called

    assert resp == result


def test_aynchronous_tasks(monkeypatch, boto_client):
    """Test asynchronous tasks.
    """

    monkeypatch.setattr(activity.ActivityExecution, 'heartbeat',
        lambda self: None)

    tasks = [MagicMock() for i in range(5)]
    tasks[2].return_value = dict(oi='mondo')
    tasks[4].return_value = dict(bonjour='monde')
    expected_response = dict(
        list(tasks[2].return_value.items()) +
        list(tasks[4].return_value.items()))

    workers = 2
    current_runner = runner.Async(*tasks, max_workers=workers)

    assert current_runner.max_workers == workers
    assert len(current_runner.tasks) == len(tasks)

    current_activity = activity.ActivityExecution(boto_client,
         'activityId', 'taskToken', '{"hello": "world"}')

    resp = current_runner.execute(current_activity, current_activity.context)

    for current_task in tasks:
        assert current_task.called

    assert resp == expected_response


def test_calculate_timeout_with_no_tasks():
    """Task list without task has no timeout.
    """

    task_list = runner.BaseRunner()
    assert task_list.timeout(EMPTY_CONTEXT) == 0


def test_calculate_heartbeat_with_no_tasks():
    """Task list without tasks has no heartbeat.
    """

    task_list = runner.BaseRunner()
    assert task_list.heartbeat(EMPTY_CONTEXT) == 0


def test_calculate_default_timeout():
    """Tasks that do not have a set timeout get the default timeout.
    """

    task_list = runner.BaseRunner(lambda x: x)
    assert task_list.timeout(EMPTY_CONTEXT) == runner.DEFAULT_TASK_TIMEOUT


def test_calculate_default_heartbeat():
    """Tasks that do not have a set timeout get the default timeout.
    """

    task_list = runner.BaseRunner(lambda x: x)
    assert task_list.heartbeat(EMPTY_CONTEXT) == runner.DEFAULT_TASK_HEARTBEAT


def test_calculate_timeout():
    """Check methods that have set timeout.
    """

    timeout = 10

    @task.timeout(timeout)
    def task_a():
        pass

    current_runner = runner.BaseRunner(task_a)
    assert current_runner.timeout(EMPTY_CONTEXT) == timeout

    @task.decorate(timeout=timeout)
    def task_b():
        pass

    current_runner = runner.BaseRunner(task_b)
    assert current_runner.timeout(EMPTY_CONTEXT) == timeout

    def task_c():
        pass

    current_runner = runner.BaseRunner(task_a, task_c)
    current_timeout = current_runner.timeout(EMPTY_CONTEXT)
    expected_timeout = timeout + runner.DEFAULT_TASK_TIMEOUT
    assert current_timeout == expected_timeout


def test_calculate_heartbeat():
    """Check methods that have set timeout.
    """

    @task.decorate(heartbeat=5)
    def task_a():
        pass

    current_runner = runner.BaseRunner(task_a)
    assert current_runner.heartbeat(EMPTY_CONTEXT) == 5

    @task.decorate(heartbeat=3)
    def task_b():
        pass

    current_runner = runner.BaseRunner(task_b)
    assert current_runner.heartbeat(EMPTY_CONTEXT) == 3

    @task.decorate(heartbeat=4498)
    def task_c():
        pass

    def task_d():
        pass

    current_runner = runner.BaseRunner(
        task_a, task_b, task_c, task_d)
    assert current_runner.heartbeat(EMPTY_CONTEXT) == 4498


def test_runner_requirements():
    """Test the requirements for the runner
    """

    @task.decorate()
    def task_a():
        pass

    @task.decorate(timeout=20)
    def task_b():
        pass

    value_1 = 'context.value'
    value_2 = 'context.value_1'
    current_runner = runner.BaseRunner(
        task_a.fill(foo=value_1),
        task_b.fill(foobar=value_2))

    requirements = current_runner.requirements(EMPTY_CONTEXT)
    assert len(requirements) == 2
    assert value_1 in requirements
    assert value_2 in requirements


def test_runner_requirements_without_decoration():
    """Should just throw an exception.
    """

    def task_a():
        pass

    current_runner = runner.BaseRunner(task_a)

    with pytest.raises(runner.NoRunnerRequirementsFound):
        current_runner.requirements(EMPTY_CONTEXT)
