import pytest
import responses
from mock import patch

from datarobot import ExternalScores
from datarobot.errors import ClientError


class TestExternalScores(object):

    @pytest.fixture()
    def scores_data(self, project_id, model_id):
        data = {'projectId': project_id,
                'modelId': model_id,
                'scores': [{'label': 'AUC', 'value': 1.0},
                           {'label': 'RMSE', 'value': 0.0606},
                           {'label': 'Kolmogorov-Smirnov', 'value': 1.0},
                           {'label': 'LogLoss', 'value': 0.01383},
                           {'label': 'FVE Binomial', 'value': -13845778590722.23},
                           {'label': 'Max MCC', 'value': 1.0}]
                }

        return data

    @staticmethod
    def create_response_data(count, scores, model_id=None, actual_value_column=None):
        data = []
        for i in range(count):
            item = dict(scores, datasetId='dataset_id_{}'.format(i),
                        actualValueColumn=actual_value_column)
            if model_id:
                item['modelId'] = model_id
            data.append(item)
        return data

    @pytest.fixture
    def url(self, project_id):
        return 'https://host_name.com/projects/{}/externalScores/'.format(project_id)

    def test_instantiation(self, scores_data):
        scores = self.create_response_data(1, scores_data, actual_value_column='label')[0]
        scores_obj = ExternalScores.from_server_data(scores)

        assert scores_obj.dataset_id == 'dataset_id_0'
        assert scores_obj.project_id == scores_data['projectId']
        assert scores_obj.model_id == scores_data['modelId']
        assert scores_obj.actual_value_column == 'label'
        assert scores_obj.scores == scores_data['scores']

    def test_future_proof(self, scores_data):
        scores = self.create_response_data(1, scores_data)[0]
        data_with_future_keys = dict(scores, new_key='some future lift data')
        ExternalScores.from_server_data(data_with_future_keys)

    @responses.activate
    def test_scores_insights_create(self, project_id, project_url, base_job_server_data):
        scores_base_url = '{}externalScores/'.format(project_url)
        job_url = '{}jobs/{}/'.format(project_url, base_job_server_data['id'])

        responses.add(responses.POST, scores_base_url,
                      body='', status=202, content_type='application/json',
                      adding_headers={'Location': job_url})
        responses.add(responses.GET, job_url, json=base_job_server_data, status=303,
                      adding_headers={'Location': job_url})
        insights_job = ExternalScores.create(project_id=project_id,
                                             model_id='578e59a41ced2e5a9eb18960',
                                             dataset_id='581c6256100d2b60586980d9')
        assert str(insights_job.id) == base_job_server_data['id']

    @responses.activate
    def test_scores_insights_create_empty_target_unsupervised(self,  project_id, project_url,
                                                              base_job_server_data):
        scores_base_url = '{}externalScores/'.format(project_url)
        job_url = '{}jobs/{}/'.format(project_url, base_job_server_data['id'])

        responses.add(responses.POST, scores_base_url,
                      body='', status=202, content_type='application/json',
                      adding_headers={'Location': job_url})
        exception_text = (
                    'Cannot compute scores and insights on this dataset because target column '
                    'is missing'
                )
        responses.add(responses.GET, job_url, json={'message': exception_text}, status=422,
                      adding_headers={'Location': job_url})
        with pytest.raises(ClientError, message=exception_text):
            ExternalScores.create(project_id=project_id,
                                  model_id='578e59a41ced2e5a9eb18960',
                                  dataset_id='581c6256100d2b60586980d9')

    @responses.activate
    @patch('datarobot.models.external_dataset_scores_insights.external_scores.DEFAULT_BATCH_SIZE',
           10)
    def test_external_scores_all_results(self, scores_data, url, project_id, model_id):
        url_page1 = url + '?modelId={}&limit=10&offset=0'.format(model_id)
        url_page2 = url + '?modelId={}&limit=10&offset=10'.format(model_id)
        all_data = self.create_response_data(11, scores_data)
        resp_data_1 = dict(count=10, previous=None, next=url_page2, data=all_data[:10])
        resp_data_2 = dict(count=1, previous=url_page1, next=None, data=all_data[10:])
        responses.add(responses.GET, url_page1, json=resp_data_1, status=200,
                      match_querystring=True)
        responses.add(responses.GET, url_page2, json=resp_data_2, status=200,
                      match_querystring=True)

        scores_list = ExternalScores.list(project_id, model_id, limit=0)
        assert len(scores_list) == 11
        for i, scores in enumerate(scores_list):
            assert isinstance(scores, ExternalScores)
            assert scores.project_id == project_id
            assert scores.model_id == model_id
            assert scores.scores == scores_data['scores']

    @responses.activate
    @patch('datarobot.models.external_dataset_scores_insights.external_scores.DEFAULT_BATCH_SIZE',
           1)
    def test_external_scores_all_results_with_offset(self, scores_data, url, project_id):
        url_page1 = url + '?datasetId={}&limit=1&offset=1'.format('dataset_id_0')
        url_page2 = url + '?datasetId={}&limit=1&offset=2'.format('dataset_id_0')
        all_data = self.create_response_data(1, scores_data, model_id='model_id_0')
        all_data.extend(self.create_response_data(1, scores_data, model_id='model_id_1'))
        all_data.extend(self.create_response_data(1, scores_data, model_id='model_id_2'))
        resp_data_1 = dict(count=1, previous=None, next=url_page2, data=[all_data[1]])
        resp_data_2 = dict(count=1, previous=url_page1, next=None, data=[all_data[2]])
        responses.add(responses.GET, url_page1, json=resp_data_1, status=200,
                      match_querystring=True)
        responses.add(responses.GET, url_page2, json=resp_data_2, status=200,
                      match_querystring=True)

        scores_list = ExternalScores.list(project_id, dataset_id='dataset_id_0', limit=0, offset=1)
        assert len(scores_list) == 2
        assert scores_list[0].dataset_id == 'dataset_id_0'
        assert scores_list[0].model_id == 'model_id_1'
        assert scores_list[1].dataset_id == 'dataset_id_0'
        assert scores_list[1].model_id == 'model_id_2'

    @responses.activate
    def test_external_scores_limited(self, scores_data, url, project_id, model_id):
        url_page = url + '?limit=3&offset=2'
        all_data = self.create_response_data(5, scores_data)
        resp_data = dict(count=3, previous=None, next=None, data=all_data[2:5])
        responses.add(responses.GET, url_page, json=resp_data, status=200,
                      match_querystring=True)
        scores_list = ExternalScores.list(project_id, limit=3, offset=2)
        assert len(scores_list) == 3
        for i, scores in enumerate(scores_list):
            assert isinstance(scores, ExternalScores)
            assert scores.dataset_id == 'dataset_id_{}'.format(i + 2)

    @responses.activate
    def test_get_scores_success(self, scores_data, url, project_id, model_id):
        dataset_id = 'dataset_id_0'
        url_page = url + '?modelId={}&datasetId={}&limit=100&offset=0'.format(model_id, dataset_id)
        all_data = self.create_response_data(2, scores_data, model_id=model_id)
        resp_data = dict(count=1, previous=None, next=None, data=[all_data[0]])
        responses.add(responses.GET, url_page, json=resp_data, status=200,
                      match_querystring=True)
        scores = ExternalScores.get(project_id, model_id, dataset_id)
        assert isinstance(scores, ExternalScores)
        assert scores.dataset_id == dataset_id
        assert scores.model_id == model_id

    @responses.activate
    def test_get_scores_failure(self, url, project_id, model_id):
        dataset_id = 'dataset_id_0'
        url_page = url + '?modelId={}&datasetId={}&limit=100&offset=0'.format(model_id, dataset_id)
        resp_data = dict(count=0, previous=None, next=None, data=[])
        responses.add(responses.GET, url_page, json=resp_data, status=200,
                      match_querystring=True)
        with pytest.raises(ValueError, match='dataset_id must be specified'):
            ExternalScores.get(project_id, model_id, None)
        with pytest.raises(ValueError, match='model_id must be specified'):
            ExternalScores.get(project_id, None, None)
        with pytest.raises(ClientError, match='Requested scores do not exist.'):
            ExternalScores.get(project_id, model_id, dataset_id)
