# -*- coding: utf-8 -*-
import json
import sys

import pytest
from apistar.exceptions import ErrorResponse

from arkindex_worker.cache import CachedElement
from arkindex_worker.worker import ActivityState, ElementsWorker

from . import BASE_API_CALLS

TEST_VERSION_ID = "test_123"
TEST_SLUG = "some_slug"


def test_get_worker_version(fake_dummy_worker):
    api_client = fake_dummy_worker.api_client

    response = {"worker": {"slug": TEST_SLUG}}

    api_client.add_response("RetrieveWorkerVersion", response, id=TEST_VERSION_ID)

    res = fake_dummy_worker.get_worker_version(TEST_VERSION_ID)

    assert res == response
    assert fake_dummy_worker._worker_version_cache[TEST_VERSION_ID] == response


def test_get_worker_version__uses_cache(fake_dummy_worker):
    api_client = fake_dummy_worker.api_client

    response = {"worker": {"slug": TEST_SLUG}}

    api_client.add_response("RetrieveWorkerVersion", response, id=TEST_VERSION_ID)

    response_1 = fake_dummy_worker.get_worker_version(TEST_VERSION_ID)
    response_2 = fake_dummy_worker.get_worker_version(TEST_VERSION_ID)

    assert response_1 == response
    assert response_1 == response_2

    # assert that only one call to the API
    assert len(api_client.history) == 1
    assert not api_client.responses


def test_get_worker_version_slug(mocker, fake_dummy_worker):
    fake_dummy_worker.get_worker_version = mocker.MagicMock()
    fake_dummy_worker.get_worker_version.return_value = {
        "id": TEST_VERSION_ID,
        "worker": {"slug": "mock_slug"},
    }

    slug = fake_dummy_worker.get_worker_version_slug(TEST_VERSION_ID)
    assert slug == "mock_slug"


def test_get_worker_version_slug_none(fake_dummy_worker):
    with pytest.raises(ValueError) as excinfo:
        fake_dummy_worker.get_worker_version_slug(None)
    assert str(excinfo.value) == "No worker version ID"


def test_readonly(responses, mock_elements_worker):
    """Test readonly worker does not trigger any API calls"""

    # Setup the worker as read-only
    mock_elements_worker.worker_version_id = None
    assert mock_elements_worker.is_read_only is True

    out = mock_elements_worker.update_activity("1234-deadbeef", ActivityState.Processed)

    assert out is None
    assert len(responses.calls) == len(BASE_API_CALLS)
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS


def test_activities_disabled(
    responses, monkeypatch, mock_worker_version_api, mock_user_api
):
    """Test worker process elements without updating activities when they are disabled for the process"""
    responses.add(
        responses.GET,
        "http://testserver/api/v1/imports/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeffff/",
        status=200,
        body=json.dumps({"activity_state": "disabled"}),
        content_type="application/json",
    )
    monkeypatch.setattr(sys, "argv", ["worker"])
    worker = ElementsWorker()
    worker.configure()
    assert not worker.is_read_only

    assert len(responses.calls) == len(BASE_API_CALLS)
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS


def test_update_call(responses, mock_elements_worker, mock_process_api):
    """Test an update call with feature enabled triggers an API call"""
    responses.add(
        responses.PUT,
        "http://testserver/api/v1/workers/versions/12341234-1234-1234-1234-123412341234/activity/",
        status=200,
        json={
            "element_id": "1234-deadbeef",
            "state": "processed",
        },
    )

    out = mock_elements_worker.update_activity("1234-deadbeef", ActivityState.Processed)

    # Check the response received by worker
    assert out == {
        "element_id": "1234-deadbeef",
        "state": "processed",
    }

    assert len(responses.calls) == len(BASE_API_CALLS) + 1
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        (
            "PUT",
            "http://testserver/api/v1/workers/versions/12341234-1234-1234-1234-123412341234/activity/",
        ),
    ]

    # Check the request sent by worker
    assert json.loads(responses.calls[-1].request.body) == {
        "element_id": "1234-deadbeef",
        "state": "processed",
    }


@pytest.mark.parametrize(
    "process_exception, final_state",
    [
        # Successful process_element
        (None, "processed"),
        # Failures in process_element
        (
            ErrorResponse(title="bad gateway", status_code=502, content="Bad gateway"),
            "error",
        ),
        (ValueError("Something bad"), "error"),
        (Exception("Any error"), "error"),
    ],
)
def test_run(
    monkeypatch, mock_elements_worker, responses, process_exception, final_state
):
    """Check the normal runtime sends 2 API calls to update activity"""
    # Disable second configure call from run()
    monkeypatch.setattr(mock_elements_worker, "configure", lambda: None)

    # Mock elements
    monkeypatch.setattr(
        mock_elements_worker,
        "list_elements",
        lambda: [
            "1234-deadbeef",
        ],
    )
    responses.add(
        responses.GET,
        "http://testserver/api/v1/element/1234-deadbeef/",
        status=200,
        json={
            "id": "1234-deadbeef",
            "type": "page",
            "name": "Test Page n°1",
        },
    )

    # Mock Update activity
    responses.add(
        responses.PUT,
        "http://testserver/api/v1/workers/versions/12341234-1234-1234-1234-123412341234/activity/",
        status=200,
        json={
            "element_id": "1234-deadbeef",
            "state": "started",
        },
    )
    responses.add(
        responses.PUT,
        "http://testserver/api/v1/workers/versions/12341234-1234-1234-1234-123412341234/activity/",
        status=200,
        json={
            "element_id": "1234-deadbeef",
            "state": final_state,
        },
    )

    assert mock_elements_worker.is_read_only is False

    # Mock exception in process_element
    if process_exception:

        def _err():
            raise process_exception

        monkeypatch.setattr(mock_elements_worker, "process_element", _err)

        # The worker stops because all elements failed !
        with pytest.raises(SystemExit):
            mock_elements_worker.run()
    else:
        # Simply run the process
        mock_elements_worker.run()

    assert len(responses.calls) == len(BASE_API_CALLS) + 3
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        ("GET", "http://testserver/api/v1/element/1234-deadbeef/"),
        (
            "PUT",
            "http://testserver/api/v1/workers/versions/12341234-1234-1234-1234-123412341234/activity/",
        ),
        (
            "PUT",
            "http://testserver/api/v1/workers/versions/12341234-1234-1234-1234-123412341234/activity/",
        ),
    ]

    # Check the requests sent by worker
    assert json.loads(responses.calls[-2].request.body) == {
        "element_id": "1234-deadbeef",
        "state": "started",
    }
    assert json.loads(responses.calls[-1].request.body) == {
        "element_id": "1234-deadbeef",
        "state": final_state,
    }


def test_run_cache(
    monkeypatch, mocker, mock_elements_worker_with_cache, mock_cached_elements
):
    # Disable second configure call from run()
    monkeypatch.setattr(mock_elements_worker_with_cache, "configure", lambda: None)

    # Make all the cached elements from the fixture initial elements
    CachedElement.update(initial=True).execute()

    mock_elements_worker_with_cache.process_element = mocker.MagicMock()
    mock_elements_worker_with_cache.run()

    assert mock_elements_worker_with_cache.process_element.call_args_list == [
        # Called once for each cached element
        mocker.call(elt)
        for elt in CachedElement.select()
    ]
