from datetime import datetime
import io
import json
import math
from threading import Thread
from time import sleep
from unittest.mock import call
from unittest.mock import Mock
from unittest.mock import patch

import dateutil.parser
from flask import request
import numpy as np
import pytest
import ujson
from werkzeug.exceptions import BadRequest


def dec(func):
    def inner(*args, **kwargs):
        """dec"""
        return func(*args, **kwargs) + '_decorated'
    return inner


class TestPredictService:
    @pytest.fixture
    def PredictService(self):
        from palladium.server import PredictService
        return PredictService

    def test_functional(self, PredictService, flask_app):
        model = Mock()
        model.threshold = 0.3
        model.size = 10
        # needed as hasattr would evaluate to True otherwise
        del model.threshold2
        del model.size2
        model.predict.return_value = np.array(['class1'])
        service = PredictService(
            mapping=[
                ('sepal length', 'float'),
                ('sepal width', 'float'),
                ('petal length', 'float'),
                ('petal width', 'float'),
                ('color', 'str'),
                ('age', 'int'),
                ('active', 'bool'),
                ('austrian', 'bool'),
                ],
            params=[
                ('threshold', 'float'),  # default will be overwritten
                ('size', 'int'),  # not provided, default value kept
                ('threshold2', 'float'),  # will be used, no default value
                ('size2', 'int'),  # not provided, no default value
                ])

        with flask_app.test_request_context():
            with patch('palladium.util.get_config') as get_config:
                get_config.return_value = {
                    'service_metadata': {
                        'service_name': 'iris',
                        'service_version': '0.1'
                    }
                }
                request = Mock(
                    args=dict([
                        ('sepal length', '5.2'),
                        ('sepal width', '3.5'),
                        ('petal length', '1.5'),
                        ('petal width', '0.2'),
                        ('color', 'purple'),
                        ('age', '1'),
                        ('active', 'True'),
                        ('austrian', 'False'),
                        ('threshold', '0.7'),
                        ('threshold2', '0.8'),
                        ]),
                    method='GET',
                    )
                resp = service(model, request)

        assert (model.predict.call_args[0][0] ==
                np.array([[5.2, 3.5, 1.5, 0.2,
                           'purple', 1, True, False]], dtype='object')).all()
        assert model.predict.call_args[1]['threshold'] == 0.7
        assert model.predict.call_args[1]['size'] == 10
        assert model.predict.call_args[1]['threshold2'] == 0.8
        assert 'size2' not in model.predict.call_args[1]
        assert resp.status_code == 200
        expected_resp_data = {
            "metadata": {
                "status": "OK",
                "error_code": 0,
                "service_name": "iris",
                "service_version": "0.1",
                },
            "result": "class1"
            }

        assert json.loads(resp.get_data(as_text=True)) == expected_resp_data

    def test_bad_request(self, PredictService, flask_app):
        predict_service = PredictService(mapping=[])
        model = Mock()
        request = Mock()
        with patch.object(predict_service, 'do') as psd:
            with flask_app.test_request_context():
                bad_request = BadRequest()
                bad_request.args = ('daniel',)
                psd.side_effect = bad_request
                resp = predict_service(model, request)
        resp_data = json.loads(resp.get_data(as_text=True))
        assert resp.status_code == 400
        assert resp_data == {
            "metadata": {
                "status": "ERROR",
                "error_code": -1,
                "error_message": "BadRequest: ('daniel',)"
                }
            }

    def test_predict_error(self, PredictService, flask_app):
        from palladium.interfaces import PredictError
        predict_service = PredictService(mapping=[])
        model = Mock()
        request = Mock()
        with patch.object(predict_service, 'do') as psd:
            with flask_app.test_request_context():
                psd.side_effect = PredictError("mymessage", 123)
                resp = predict_service(model, request)
        resp_data = json.loads(resp.get_data(as_text=True))
        assert resp.status_code == 500
        assert resp_data == {
            "metadata": {
                "status": "ERROR",
                "error_code": 123,
                "error_message": "mymessage",