from collections import OrderedDict
import json

from civis import response
from civis.base import CivisAPIError
from civis.compat import mock
from civis.service_client import ServiceClient, ServiceEndpoint, _get_service
import pytest

MOCK_SERVICE_ID = 0

MOCK_URL = "www.survey-url.com"


@pytest.fixture
def mock_swagger():
    return {
            "info": {
                "title": "Test API Client",
                "version": "1.0"
            },
            "paths": {
                "/some-resources": {
                    "get": {
                        "description": "",
                        "responses": {
                            "200": {
                                "description": "Returns a list",
                            }
                        },
                        "summary": "List Resources",
                        "tags": [
                            "tag"
                        ]
                    }
                },
                "/some-resources/{id}": {
                    "get": {
                        "description": "",
                        "responses": {
                            "200": {
                                "description": "Returns a Resource",
                            }
                        },
                        "summary": "Get Resources",
                        "tags": [
                            "tag"
                        ]
                    },
                    "patch": {
                        "description": "",
                        "parameters": [
                            {
                                "description": "The id of the Resource",
                                "in": "path",
                                "name": "id",
                                "required": True,
                                "type": "integer"
                            },
                            {
                                "description": "The fields and values to edit",
                                "in": "body",
                                "name": "body",
                                "required": True,
                                "schema": {
                                    "properties": {
                                        "field": {
                                            "description": "a property value",
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        ],
                        "responses": {
                            "200": {
                                "description": "Edits Resource",
                            }
                        },
                        "summary": "Patch Resources",
                        "tags": [
                            "tag"
                        ]
                    }
                }
            },
            "swagger": "2.0"
            }


@pytest.fixture
def mock_operations(mock_swagger):
    ops_json = mock_swagger["paths"]["/some-resources"]
    mock_ops_str = str(json.dumps(ops_json))
    mock_operations = json.JSONDecoder(object_pairs_hook=OrderedDict).decode(mock_ops_str)  # noqa: E501
    return mock_operations


@mock.patch('civis.service_client.ServiceClient.generate_classes_maybe_cached')
@mock.patch('civis.service_client.ServiceClient.get_base_url')
def test_service_client(url_mock, classes_mock):
    url_mock.return_value = MOCK_URL
    classes_mock.return_value = {}

    sc = ServiceClient(MOCK_SERVICE_ID)

    spec_endpoint = "/endpoints"

    assert sc._api_key is None
    assert sc._service_id == MOCK_SERVICE_ID
    assert sc._base_url == MOCK_URL
    assert sc._root_path is None
    assert sc._swagger_path == spec_endpoint

    # Custom root path
    sc = ServiceClient(MOCK_SERVICE_ID, root_path='/api')
    assert sc._root_path == '/api'

    # Custom Swagger path
    sc = ServiceClient(MOCK_SERVICE_ID, swagger_path='/spec')
    assert sc._swagger_path == "/spec"

    # Passed in API Key
    sc = ServiceClient(MOCK_SERVICE_ID, api_key="this_is_an_API_key")
    assert sc._api_key == "this_is_an_API_key"


def test_service_endpoint():
    service_client_mock = mock.Mock()
    se = ServiceEndpoint(service_client_mock)

    assert se._return_type == 'civis'
    assert se._client == service_client_mock


@mock.patch('civis.service_client.ServiceClient.generate_classes_maybe_cached')
@mock.patch('civis.service_client.ServiceClient.get_base_url')
def test_parse_path(url_mock, classes_mock, mock_operations):
    url_mock.return_value = MOCK_URL
    classes_mock.return_value = {}
    sc = ServiceClient(MOCK_SERVICE_ID)

    mock_path = '/some-resource/sub-resource/{id}'
    base_path, methods = sc.parse_path(mock_path, mock_operations)

    assert base_path == "some_resource"
    assert 'get_sub_resource' in methods[0]

    mock_path = '/some-resource/{id}'
    base_path, methods = sc.parse_path(mock_path, mock_operations)

    assert base_path == "some_resource"
    assert 'get' in methods[0]


@mock.patch('civis.service_client.ServiceClient.generate_classes_maybe_cached')
@mock.patch('civis.service_client.ServiceClient.get_base_url')
def test_parse_path__with_root(url_mock, classes_mock, mock_operations):
    url_mock.return_value = MOCK_URL
    classes_mock.return_value = {}
    sc = ServiceClient(MOCK_SERVICE_ID, root_path='/some-resource')

    mock_path = '/some-resource/sub-resource/{id}'
    base_path, methods = sc.parse_path(mock_path, mock_operations)

    assert base_path == "sub_resource"
    assert 'get' in methods[0]


@mock.patch('civis.service_client.ServiceClient.generate_classes_maybe_cached')
@mock.patch('civis.service_client.ServiceClient.get_base_url')
def test_parse_api_spec(url_mock, classes_mock, mock_swagger):
    url_mock.return_value = MOCK_URL
    classes_mock.return_value = {}

    sc = ServiceClient(MOCK_SERVICE_ID)

    classes = sc.parse_api_spec(mock_swagger)
    assert 'some_resources' in classes


@mock.patch('civis.service_client.requests.Session.get')
@mock.patch('civis.service_client.auth_service_session')
@mock.patch('civis.service_client.ServiceClient.generate_classes_maybe_cached')
@mock.patch('civis.service_client.ServiceClient.get_base_url')
def test_get_api_spec(url_mock, classes_mock,
                      auth_session_mock, mock_response, mock_swagger):
    mock_response.return_value = mock.Mock(ok=True)
    mock_response.return_value.json.return_value = mock_swagger

    url_mock.return_value = MOCK_URL
    classes_mock.return_value = {}

    sc = ServiceClient(MOCK_SERVICE_ID)

    spec = sc.get_api_spec()
    assert spec == mock_swagger


@mock.patch('civis.service_client.ServiceClient.parse_api_spec')
@mock.patch('civis.service_client.ServiceClient.get_api_spec')
@mock.patch('civis.service_client.ServiceClient.get_base_url')
def test_generate_classes(url_mock, api_spec_mock,
                          parse_mock, mock_swagger):
    api_spec_mock.return_value = {}
    mock_class_function = (lambda client, return_type: "return")
    parse_mock.return_value = {'class': mock_class_function}
    url_mock.return_value = MOCK_URL

    sc = ServiceClient(MOCK_SERVICE_ID)

    classes = sc.generate_classes()

    assert 'class' in classes


@mock.patch('civis.service_client.ServiceClient.parse_api_spec')
@mock.patch('civis.service_client.ServiceClient.get_api_spec')
@mock.patch('civis.service_client.ServiceClient.get_base_url')
def test_generate_classes_maybe_cached(url_mock, api_spec_mock,
                                       parse_mock, mock_swagger):
    api_spec_mock.return_value = {}
    mock_class_function = (lambda client, return_type: "return")
    parse_mock.return_value = {'class': mock_class_function}
    url_mock.return_value = MOCK_URL

    sc = ServiceClient(MOCK_SERVICE_ID)

    mock_spec_str = str(json.dumps(mock_swagger))
    mock_spec = json.JSONDecoder(object_pairs_hook=OrderedDict).decode(mock_spec_str)  # noqa: E501
    classes = sc.generate_classes_maybe_cached(mock_spec)

    assert 'class' in classes


@mock.patch('civis.service_client.ServiceClient.generate_classes_maybe_cached')
@mock.patch('civis.service_client._get_service')
def test_get_base_url(get_service_mock, classes_mock):
    get_service_mock.return_value = {'current_url': MOCK_URL}
    classes_mock.return_value = {}

    sc = ServiceClient(MOCK_SERVICE_ID)

    assert sc._base_url == MOCK_URL
    get_service_mock.assert_called_once_with(sc)


@mock.patch('civis.service_client.ServiceClient.generate_classes_maybe_cached')
@mock.patch('civis.service_client.APIClient')
def test_get_service(mock_client, classes_mock):
    classes_mock.return_value = {}
    sc = ServiceClient(MOCK_SERVICE_ID)
    expected_service = {'current_url': MOCK_URL}
    mock_client.return_value.services.get.return_value = expected_service
    service = _get_service(sc)
    assert service == expected_service


@mock.patch('civis.service_client.ServiceClient.generate_classes_maybe_cached')
@mock.patch('civis.service_client.APIClient')
def test_get_service__not_found(mock_client, classes_mock):
    classes_mock.return_value = {}
    sc = ServiceClient(MOCK_SERVICE_ID)
    err_resp = response.Response({
        'status_code': 404,
        'error': 'not_found',
        'errorDescription': 'The requested resource could not be found.',
        'content': True})
    err_resp.json = lambda: err_resp.json_data

    mock_client.return_value.services.get.side_effect = CivisAPIError(err_resp)

    with pytest.raises(CivisAPIError) as excinfo:
        _get_service(sc)

    expected_error = ('(404) The requested resource could not be found.')
    assert str(excinfo.value) == expected_error


def test_build_path():
    service_client_mock = mock.Mock(_base_url='www.service_url.com',
                                    _root_path=None)
    se = ServiceEndpoint(service_client_mock)
    path = se._build_path('/resources')

    assert path == 'www.service_url.com/resources'


def test_build_path__with_root():
    service_client_mock = mock.Mock(_base_url='www.service_url.com',
                                    _root_path='/api')
    se = ServiceEndpoint(service_client_mock)
    path = se._build_path('/resources')

    assert path == 'www.service_url.com/api/resources'


@mock.patch('civis.service_client.requests.Session.request')
@mock.patch('civis.service_client.auth_service_session')
def test_make_request(auth_mock, request_mock):
    service_client_mock = mock.Mock(_base_url='www.service_url.com')
    se = ServiceEndpoint(service_client_mock)

    expected_value = [{'id': 1, 'url': 'www.survey_url.com/1'},
                      {'id': 2, 'url': 'www.survey_url.com/2'}]

    request_mock.return_value = mock.Mock(ok=True)
    request_mock.return_value.json = expected_value

    response = se._make_request('get', 'resources/resources')

    assert response.json == expected_value
