import json
from collections import OrderedDict

import dateutil
import pytest
import responses

from datarobot.models import Deployment, ServiceStats, ServiceStatsOverTime
from datarobot.utils import from_api


class TestServiceStats(object):
    @pytest.fixture
    def response_data(self, deployment_data):
        return {
            'model_id': deployment_data['model']['id'],
            'period': {
                'start': '2019-08-01T00:00:00.000000Z',
                'end': '2019-08-10T00:00:00.000000Z'
            },
            'metrics': {
                'totalPredictions': 1000,
                'totalRequests': 50,
            }
        }

    @pytest.fixture
    def response(self, unittest_endpoint, deployment_data, response_data):
        url = '{}/deployments/{}/serviceStats/'.format(unittest_endpoint, deployment_data['id'])
        responses.add(
            responses.GET,
            url,
            status=200,
            content_type='application/json',
            body=json.dumps(response_data)
        )

    @responses.activate
    @pytest.mark.usefixtures('deployment_get_response', 'response')
    def test_service_stats(self, deployment_data, response_data):
        service_stats_objs = [
            ServiceStats.get(deployment_data['id']),
            Deployment.get(deployment_data['id']).get_service_stats()
        ]
        for service_stats in service_stats_objs:
            assert service_stats.model_id == response_data['model_id']

            expected_start_date = dateutil.parser.parse(response_data['period']['start'])
            expected_end_date = dateutil.parser.parse(response_data['period']['end'])
            assert service_stats.period['start'] == expected_start_date
            assert service_stats.period['end'] == expected_end_date

            assert service_stats.metrics.keys() == response_data['metrics'].keys()
            for metric_name, metric in service_stats.metrics.items():
                expected_value = response_data['metrics'][metric_name]
                assert metric == expected_value
                assert service_stats[metric_name] == expected_value

    @responses.activate
    @pytest.mark.usefixtures('deployment_get_response', 'response')
    @pytest.mark.parametrize('kwarg, expected_query_string', [
        ({'model_id': 'abc'}, '?modelId=abc'),
        ({'execution_time_quantile': 0.74}, '?executionTimeQuantile=0.74'),
        ({'response_time_quantile': 0.43}, '?responseTimeQuantile=0.43'),
        ({'slow_requests_threshold': 40}, '?slowRequestsThreshold=40'),
    ])
    def test_params(self, deployment_data, kwarg, expected_query_string):
        """Test optional params are correctly forwarded to the API"""

        ServiceStats.get(deployment_data['id'], **kwarg)
        assert expected_query_string in responses.calls[0].request.url

        Deployment.get(deployment_data['id']).get_service_stats(**kwarg)
        assert expected_query_string in responses.calls[0].request.url


class TestServiceStatsOverTime(object):
    @pytest.fixture
    def response_data(self, deployment_data):
        return {
            'model_id': deployment_data['model']['id'],
            'metric': 'totalPredictions',
            'summary': {
                'period': {
                    'start': '2019-08-01T00:00:00.000000Z',
                    'end': '2019-08-04T00:00:00.000000Z'
                },
                'value': 2250,
            },
            'buckets': [
                {
                    'period': {
                        'start': '2019-08-01T00:00:00.000000Z',
                        'end': '2019-08-02T00:00:00.000000Z'
                    },
                    'value': 1250,
                }, {
                    'period': {
                        'start': '2019-08-02T00:00:00.000000Z',
                        'end': '2019-08-03T00:00:00.000000Z'
                    },
                    'value': 1000,
                }, {
                    'period': {
                        'start': '2019-08-03T00:00:00.000000Z',
                        'end': '2019-08-04T00:00:00.000000Z'
                    },
                    'value': 0,
                }
            ]
        }

    @pytest.fixture
    def response(self, unittest_endpoint, deployment_data, response_data):
        deployment_id = deployment_data['id']
        url = '{}/deployments/{}/serviceStatsOverTime/'.format(unittest_endpoint, deployment_id)
        responses.add(
            responses.GET,
            url,
            status=200,
            content_type='application/json',
            body=json.dumps(response_data)
        )

    def assert_bucket(self, actual, expected):
        assert actual.keys() == expected.keys()

        actual_period = actual['period']
        if actual_period:
            assert actual['period'].keys() == expected['period'].keys()
            assert actual_period['start'] == dateutil.parser.parse(expected['period']['start'])
            assert actual_period['end'] == dateutil.parser.parse(expected['period']['end'])
        else:
            assert actual_period is None

        assert actual['value'] == expected['value']

    @responses.activate
    @pytest.mark.usefixtures('deployment_get_response', 'response')
    def test_service_stats_over_time(self, deployment_data, response_data):
        service_stats_over_time_objs = [
            ServiceStatsOverTime.get(deployment_data['id']),
            Deployment.get(deployment_data['id']).get_service_stats_over_time()
        ]
        for service_stats_over_time in service_stats_over_time_objs:
            expected = from_api(response_data, keep_null_keys=True)
            assert service_stats_over_time.metric == expected['metric']
            assert service_stats_over_time.model_id == expected['model_id']

            self.assert_bucket(service_stats_over_time.summary, expected['summary'])
            for index, bucket in enumerate(service_stats_over_time.buckets):
                self.assert_bucket(bucket, expected['buckets'][index])

            expected_bucket_values = [
                (dateutil.parser.parse(bucket['period']['start']), bucket['value'])
                for bucket in expected['buckets']]
            assert service_stats_over_time.bucket_values == OrderedDict(expected_bucket_values)

    @responses.activate
    @pytest.mark.usefixtures('deployment_get_response', 'response')
    @pytest.mark.parametrize('kwarg, expected_query_string', [
        ({'metric': 'LogLoss'}, '?metric=LogLoss'),
        ({'model_id': 'abc'}, '?modelId=abc'),
        ({'bucket_size': 'P1D'}, '?bucketSize=P1D'),
        ({'quantile': 0.74}, '?quantile=0.74'),
        ({'threshold': 0.43}, '?threshold=0.43'),
    ])
    def test_params(self, deployment_data, kwarg, expected_query_string):
        """Test optional params are correctly forwarded to the API"""

        ServiceStatsOverTime.get(deployment_data['id'], **kwarg)
        assert expected_query_string in responses.calls[0].request.url

        Deployment.get(deployment_data['id']).get_service_stats_over_time(**kwarg)
        assert expected_query_string in responses.calls[0].request.url
