import datetime
import unittest

import pandas as pd
import pytest
import responses

from datarobot import errors
from datarobot.client import Client, set_client
from datarobot.errors import DataRobotDeprecationWarning
from datarobot.utils import (
    dataframe_to_buffer,
    deprecation,
    from_api,
    get_id_from_response,
    is_urlsource,
    parse_time,
    recognize_sourcedata,
    to_api,
)
from datarobot.utils.sourcedata import list_of_records_to_buffer
from tests.test_helpers import fixture_file_path
from tests.utils import warns


class TestDataframeSerialization(unittest.TestCase):
    def test_list_of_records_to_buffer(self):
        records = [{"a": "b", "c": "d"}, {"a": "x", "c": "y"}]
        expected = "a,c\r\nb,d\r\nx,y\r\n"
        buff = list_of_records_to_buffer(records)
        self.assertEqual(buff.read(), expected)

    def test_no_index_please(self):
        df = pd.DataFrame({"a": range(100), "b": range(100)})
        buff = dataframe_to_buffer(df)
        readback = pd.read_csv(buff)
        self.assertEqual(readback.columns.tolist(), ["a", "b"])

    def test_parse_time(self):
        self.assertEqual("BAD TIME", parse_time("BAD TIME"))  # returns value
        test_string_time = datetime.datetime.now().isoformat()
        self.assertIsInstance(test_string_time, str)
        self.assertIsInstance(parse_time(test_string_time), datetime.datetime)

    @responses.activate
    @pytest.mark.usefixtures("no_client_version_check")
    def test_get_id_from_response_location_header(self):
        responses.add(
            responses.POST,
            "http://nothing/",
            body="",
            adding_headers={"Location": "http://nothing/f-id/"},
        )
        client = set_client(Client(token="no_matter", endpoint="http://nothing"))
        resp = client.post("")
        self.assertEqual(get_id_from_response(resp), "f-id")

    def test_csv_buffer_is_quoted(self):
        """"
        This tests that dataframes are correctly quoted when saved back to csv, so
        that columns containing line endings won't be interpreted as multiple rows
        See: PRED-3203
        """
        df = pd.DataFrame({"A": ["test\rtest", 2], "B": ["test\n\n\n\r\ntest", 0.75]})
        buff = dataframe_to_buffer(df)
        readback = pd.read_csv(buff)
        assert df.shape[0] == readback.shape[0]


class TestFromAPI(unittest.TestCase):
    def test_nested_list_of_objects_all_changed(self):
        source = {"oneFish": [{"twoFish": "redFish"}, {"blueFish": "noFish"}]}
        result = from_api(source)
        inner = result["one_fish"]
        self.assertEqual(inner[0]["two_fish"], "redFish")
        self.assertEqual(inner[1]["blue_fish"], "noFish")

    def test_nested_objects_all_changed(self):
        source = {"oneFish": {"twoFish": "redFish"}}

        result = from_api(source)
        self.assertEqual(result["one_fish"]["two_fish"], "redFish")


def test_from_api_filter_nones():
    data_in = {
        "someValue": {"nestedNone": None, "nestedNotNone": ""},
        "listNone": [{"someValue": None, "someNotNone": ""}],
        "otherNone": None,
    }
    data_out = {"some_value": {"nested_not_none": ""}, "list_none": [{"some_not_none": ""}]}
    assert data_out == from_api(data_in)


def test_from_api_no_filter_nones():
    data_in = {
        "someValue": {"nestedNone": None, "nestedNotNone": ""},
        "listNone": [{"someValue": None, "someNotNone": ""}],
        "otherNone": None,
    }
    data_out = {
        "some_value": {"nested_none": None, "nested_not_none": ""},
        "list_none": [{"some_value": None, "some_not_none": ""}],
        "other_none": None,
    }
    assert data_out == from_api(data_in, keep_null_keys=True)


def test_from_api_keep_nones():
    data_in = {
        "someValue": {"nestedNone": None, "nestedNotNone": "", "levelTwo": {"levelThree": None}},
        "otherNone": None,
        "keepLevelOne": None,
    }
    data_out = {
        "some_value": {
            "nested_not_none": "",
            "nested_none": None,
            "level_two": {"level_three": None},
        },
        "keep_level_one": None,
    }
    assert data_out == from_api(
        data_in,
        keep_attrs=["some_value.nested_none", "some_value.level_two.level_three", "keep_level_one"],
    )


def test_from_api_multiple_underscore_ignored():
    in_data = {"samplePct__gte": 64}
    expected = {"sample_pct__gte": 64}
    assert from_api(in_data) == expected


def test_to_api_multiple_underscore_ignored():
    in_data = {"sample_pct__gte": 64}
    expected = {"samplePct__gte": 64}
    assert to_api(in_data) == expected


def test_from_api_single_letter():
    in_data = {"iAmAKey": "test"}
    expected = {"i_am_a_key": "test"}
    assert from_api(in_data) == expected


def test_to_api_single_letter():
    in_data = {"i_am_a_key": "test"}
    expected = {"iAmAKey": "test"}
    assert to_api(in_data) == expected


@deprecation.deprecated(deprecated_since_version="v0.1.2", will_remove_version="v1.2.3")
def bar(arg1, arg2):
    """
    A dummy function to use for testing the deprecation mechanisms

    Parameters
    ----------
    arg1 : int
    arg2 : int

    Returns
    -------
    total : int
        arg1 + arg2

    """
    return arg1 + arg2


@deprecation.deprecated(
    deprecated_since_version="v0.1.2",
    will_remove_version="v1.2.3",
    message="Use of `bar` is recommended instead.",
)
def foo(arg1, arg2):
    """
    A dummy function to use for testing the deprecation mechanisms

    Parameters
    ----------
    arg1 : int
    arg2 : int

    Returns
    -------
    total : int
        foo + bar

    """
    return arg1 + arg2


def test_deprecation_with_message(known_warning):
    foo(1, 2)
    w_msg = known_warning[0].message.args[0]
    assert (
        w_msg == "`foo` has been deprecated in `v0.1.2`, will be removed "
        "in `v1.2.3`. Use of `bar` is recommended instead."
    )


def test_deprecation_message(known_warning):
    bar(1, 2)
    w_instance = known_warning[0].message
    assert isinstance(w_instance, DataRobotDeprecationWarning)


class TestSourcedataUtils(unittest.TestCase):
    def setUp(self):
        self.default_fname = "predict.csv"

    def test_recognize_sourcedata_passed_dataframe(self):
        df = pd.DataFrame({"a": range(100), "b": range(100)})
        kwargs = recognize_sourcedata(df, self.default_fname)
        self.assertTrue("filelike" in kwargs)
        self.assertEqual(kwargs.get("fname"), self.default_fname)
        buff = kwargs["filelike"]
        readback = pd.read_csv(buff)
        self.assertEqual(readback.columns.tolist(), ["a", "b"])

    def test_recognize_sourcedata_passed_filelike(self):
        path = fixture_file_path("synthetic-100.csv")
        with open(path, "rb") as fd:
            kwargs = recognize_sourcedata(fd, self.default_fname)
            self.assertTrue(kwargs.get("filelike") is fd)
            self.assertEqual(kwargs.get("fname"), self.default_fname)

    def test_recognize_sourcedata_passed_filepath(self):
        file_path = fixture_file_path("synthetic-100.csv")
        kwargs = recognize_sourcedata(file_path, self.default_fname)
        self.assertEqual(kwargs.get("file_path"), file_path)
        self.assertEqual(kwargs.get("fname"), "synthetic-100.csv")

    def test_recognize_sourcedata_passed_content(self):
        content = b"a,b,c\n" + b"1,2,3\n" * 100
        kwargs = recognize_sourcedata(content, self.default_fname)
        self.assertEqual(kwargs.get("content"), content)
        self.assertEqual(kwargs.get("fname"), self.default_fname)

    def test_is_urlsource_passed_true(self):
        result = is_urlsource("http://path_to_urlsource")
        self.assertTrue(result)

    def test_is_urlsource_file_passed_true(self):
        result = is_urlsource("file://path_to_urlsource")
        self.assertTrue(result)

    def test_is_urlsource_file_passed_false(self):
        result = is_urlsource("filename")
        self.assertFalse(result)

    def test_is_urlsource_passed_false(self):
        result = is_urlsource("not_a_path_to_urlsource")
        self.assertFalse(result)

    def test_is_urlsource_s3_passed_true(self):
        result = is_urlsource("s3://path/to/a/bucket/data.csv")
        self.assertTrue(result)

    def test_fatfingered_filepath_raises(self):
        content = b"/home/datarobot/mistypefilepath.csv"
        with self.assertRaises(errors.InputNotUnderstoodError):
            recognize_sourcedata(content, self.default_fname)


def test_resource():
    """Python 3 ResourceWarning of implicitly closed files."""
    with warns():
        open(__file__)


class TestImports(unittest.TestCase):
    def test_rename(self):
        """datarobot_sdk rename."""
        with self.assertRaises(ImportError):
            import datarobot_sdk  # noqa
