from collections import OrderedDict
import json

import dateutil
import pandas as pd
import pytest
import responses

from datarobot import Deployment
from datarobot.enums import ACCURACY_METRIC
from datarobot.models.deployment import Accuracy, AccuracyOverTime
from datarobot.utils import from_api


@pytest.fixture
def accuracy_response_data(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": {
            "AUC": {"baselineValue": 1, "value": 1.5, "percentChange": 50},
            "LogLoss": {"baselineValue": None, "value": None, "percentChange": None},
        },
    }


@pytest.fixture
def deployment_get_accuracy_response(unittest_endpoint, deployment_data, accuracy_response_data):
    url = "{}/deployments/{}/accuracy/".format(unittest_endpoint, deployment_data["id"])
    responses.add(
        responses.GET,
        url,
        status=200,
        content_type="application/json",
        body=json.dumps(accuracy_response_data),
    )


@pytest.fixture
def accuracy_over_time_response_data(deployment_data):
    return {
        "model_id": deployment_data["model"]["id"],
        "metric": "LogLoss",
        "summary": {
            "period": {
                "start": "2019-08-01T00:00:00.000000Z",
                "end": "2019-08-04T00:00:00.000000Z",
            },
            "value": 1,
            "sampleSize": 100,
        },
        "baseline": {"period": None, "value": None, "sampleSize": 0},
        "buckets": [
            {
                "period": {
                    "start": "2019-08-01T00:00:00.000000Z",
                    "end": "2019-08-02T00:00:00.000000Z",
                },
                "value": 0.22,
                "sampleSize": 30,
            },
            {
                "period": {
                    "start": "2019-08-02T00:00:00.000000Z",
                    "end": "2019-08-03T00:00:00.000000Z",
                },
                "value": 0.42,
                "sampleSize": 70,
            },
            {
                "period": {
                    "start": "2019-08-03T00:00:00.000000Z",
                    "end": "2019-08-04T00:00:00.000000Z",
                },
                "value": None,
                "sampleSize": 0,
            },
        ],
    }


@pytest.fixture
def deployment_get_accuracy_over_time_response(
    unittest_endpoint, deployment_data, accuracy_over_time_response_data
):
    url = "{}/deployments/{}/accuracyOverTime/".format(unittest_endpoint, deployment_data["id"])
    responses.add(
        responses.GET,
        url,
        status=200,
        content_type="application/json",
        body=json.dumps(accuracy_over_time_response_data),
    )


def assert_bucket(actual, expected):
    assert sorted(actual.keys()) == sorted(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"]
    assert actual["sample_size"] == expected["sample_size"]


@responses.activate
@pytest.mark.usefixtures("deployment_get_response", "deployment_get_accuracy_response")
def test_accuracy(deployment_data, accuracy_response_data):
    accuracy_objs = [
        Accuracy.get(deployment_data["id"]),
        Deployment.get(deployment_data["id"]).get_accuracy(),
    ]
    for accuracy in accuracy_objs:
        assert accuracy.model_id == accuracy_response_data["model_id"]

        expected_start_date = dateutil.parser.parse(accuracy_response_data["period"]["start"])
        expected_end_date = dateutil.parser.parse(accuracy_response_data["period"]["end"])
        assert accuracy.period["start"] == expected_start_date
        assert accuracy.period["end"] == expected_end_date

        assert accuracy.metrics.keys() == accuracy_response_data["metrics"].keys()
        for metric_name, metric in accuracy.metrics.items():
            expected_metric = from_api(
                accuracy_response_data["metrics"][metric_name], keep_null_keys=True
            )
            assert metric == expected_metric
            assert accuracy[metric_name] == expected_metric["value"]

        expected_metric_values = {
            name: metric["value"] for name, metric in accuracy_response_data["metrics"].items()
        }
        assert accuracy.metric_values == expected_metric_values
        expected_metric_baseline = {
            name: metric["baselineValue"]
            for name, metric in accuracy_response_data["metrics"].items()
        }
        assert accuracy.metric_baselines == expected_metric_baseline
        expected_percent_changes = {
            name: metric["percentChange"]
            for name, metric in accuracy_response_data["metrics"].items()
        }
        assert accuracy.percent_changes == expected_percent_changes


@responses.activate
@pytest.mark.usefixtures("deployment_get_response", "deployment_get_accuracy_over_time_response")
def test_accuracy_over_time(deployment_data, accuracy_over_time_response_data):
    accuracy_objs = [
        AccuracyOverTime.get(deployment_data["id"]),
        Deployment.get(deployment_data["id"]).get_accuracy_over_time(),
    ]
    for accuracy_over_time in accuracy_objs:
        expected = from_api(accuracy_over_time_response_data, keep_null_keys=True)
        assert accuracy_over_time.metric == expected["metric"]
        assert accuracy_over_time.model_id == expected["model_id"]

        assert_bucket(accuracy_over_time.summary, expected["summary"])
        assert_bucket(accuracy_over_time.baseline, expected["baseline"])
        for index, bucket in enumerate(accuracy_over_time.buckets):
            assert_bucket(bucket, expected["buckets"][index])

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

        expected_bucket_sample_sizes = [
            (dateutil.parser.parse(bucket["period"]["start"]), bucket["sample_size"])
            for bucket in expected["buckets"]
        ]
        assert accuracy_over_time.bucket_sample_sizes == OrderedDict(expected_bucket_sample_sizes)


@responses.activate
@pytest.mark.usefixtures("deployment_get_accuracy_over_time_response")
def test_accuracy_over_time_retrieve_as_dataframe(
    deployment_data, accuracy_over_time_response_data
):
    metric_names = [ACCURACY_METRIC.LOGLOSS, ACCURACY_METRIC.LOGLOSS]
    actual = AccuracyOverTime.get_as_dataframe(deployment_data["id"], metrics=metric_names)

    data = []
    start_times = []
    for bucket in accuracy_over_time_response_data["buckets"]:
        start_time = dateutil.parser.parse(bucket["period"]["start"])
        start_times.append(start_time)
        data.append([bucket["value"]] * 2)
    expected = pd.DataFrame(data, index=start_times, columns=metric_names)

    assert actual.equals(expected)
