from unittest import TestCase

from requests.exceptions import HTTPError

from genologics.lims import Lims

try:
    callable(1)
except NameError:  # callable() doesn't exist in Python 3.0 and 3.1
    import collections

    def callable(obj):
        return isinstance(obj, collections.Callable)


import builtins
from unittest.mock import Mock, patch


class TestLims(TestCase):
    url = "http://testgenologics.com:4040"
    username = "test"
    password = "password"
    sample_xml = f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<smp:samples xmlns:smp="http://genologics.com/ri/sample">
    <sample uri="{url}/api/v2/samples/test_sample" limsid="test_id"/>
</smp:samples>
"""
    error_xml = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<exc:exception xmlns:exc="http://genologics.com/ri/exception">
    <message>Generic error message</message>
</exc:exception>"""
    error_no_msg_xml = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<exc:exception xmlns:exc="http://genologics.com/ri/exception">
</exc:exception>"""

    def test_get_uri(self):
        lims = Lims(self.url, username=self.username, password=self.password)
        assert (
            lims.get_uri("artifacts", sample_name="test_sample")
            == f"{self.url}/api/v2/artifacts?sample_name=test_sample"
        )

    def test_parse_response(self):
        lims = Lims(self.url, username=self.username, password=self.password)
        r = Mock(content=self.sample_xml, status_code=200)
        pr = lims.parse_response(r)
        assert pr is not None
        assert callable(pr.find)
        assert hasattr(pr.attrib, "__getitem__")

        r = Mock(content=self.error_xml, status_code=400)
        self.assertRaises(HTTPError, lims.parse_response, r)

        r = Mock(content=self.error_no_msg_xml, status_code=400)
        self.assertRaises(HTTPError, lims.parse_response, r)

    @patch(
        "requests.Session.get", return_value=Mock(content=sample_xml, status_code=200)
    )
    def test_get(self, mocked_instance):
        lims = Lims(self.url, username=self.username, password=self.password)
        r = lims.get(f"{self.url}/api/v2/artifacts?sample_name=test_sample")
        assert r is not None
        assert callable(r.find)
        assert hasattr(r.attrib, "__getitem__")
        assert mocked_instance.call_count == 1
        mocked_instance.assert_called_with(
            "http://testgenologics.com:4040/api/v2/artifacts?sample_name=test_sample",
            timeout=16,
            headers={"accept": "application/xml"},
            params={},
            auth=("test", "password"),
        )

    def test_put(self):
        lims = Lims(self.url, username=self.username, password=self.password)
        uri = f"{self.url}/api/v2/samples/test_sample"
        with patch(
            "requests.put", return_value=Mock(content=self.sample_xml, status_code=200)
        ) as mocked_put:
            lims.put(uri=uri, data=self.sample_xml)
            assert mocked_put.call_count == 1
        with patch(
            "requests.put", return_value=Mock(content=self.error_xml, status_code=400)
        ) as mocked_put:
            self.assertRaises(HTTPError, lims.put, uri=uri, data=self.sample_xml)
            assert mocked_put.call_count == 1

    def test_post(self):
        lims = Lims(self.url, username=self.username, password=self.password)
        uri = f"{self.url}/api/v2/samples"
        with patch(
            "requests.post", return_value=Mock(content=self.sample_xml, status_code=200)
        ) as mocked_put:
            lims.post(uri=uri, data=self.sample_xml)
            assert mocked_put.call_count == 1
        with patch(
            "requests.post", return_value=Mock(content=self.error_xml, status_code=400)
        ) as mocked_put:
            self.assertRaises(HTTPError, lims.post, uri=uri, data=self.sample_xml)
            assert mocked_put.call_count == 1

    @patch("os.path.isfile", return_value=True)
    @patch.object(builtins, "open")
    def test_upload_new_file(self, mocked_open, mocked_isfile):
        lims = Lims(self.url, username=self.username, password=self.password)
        xml_intro = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"""
        file_start = """<file:file xmlns:file="http://genologics.com/ri/file">"""
        file_start2 = """<file:file xmlns:file="http://genologics.com/ri/file" uri="{url}/api/v2/files/40-3501" limsid="40-3501">"""
        attached = """    <attached-to>{url}/api/v2/samples/test_sample</attached-to>"""
        upload = """    <original-location>filename_to_upload</original-location>"""
        content_loc = """    <content-location>sftp://{url}/opt/gls/clarity/users/glsftp/clarity/samples/test_sample/test</content-location>"""
        file_end = """</file:file>"""
        glsstorage_xml = "\n".join(
            [xml_intro, file_start, attached, upload, content_loc, file_end]
        ).format(url=self.url)
        file_post_xml = "\n".join(
            [xml_intro, file_start2, attached, upload, content_loc, file_end]
        ).format(url=self.url)
        with patch(
            "requests.post",
            side_effect=[
                Mock(content=glsstorage_xml, status_code=200),
                Mock(content=file_post_xml, status_code=200),
                Mock(content="", status_code=200),
            ],
        ):
            file = lims.upload_new_file(
                Mock(uri=self.url + "/api/v2/samples/test_sample"), "filename_to_upload"
            )
            assert file.id == "40-3501"

        with patch(
            "requests.post", side_effect=[Mock(content=self.error_xml, status_code=400)]
        ):
            self.assertRaises(
                HTTPError,
                lims.upload_new_file,
                Mock(uri=self.url + "/api/v2/samples/test_sample"),
                "filename_to_upload",
            )

    @patch("requests.post", return_value=Mock(content=sample_xml, status_code=200))
    def test_route_artifact(self, mocked_post):
        lims = Lims(self.url, username=self.username, password=self.password)
        artifact = Mock(uri=self.url + "/artifact/2")
        lims.route_artifacts(
            artifact_list=[artifact],
            workflow_uri=self.url + "/api/v2/configuration/workflows/1",
        )
        assert mocked_post.call_count == 1

    def test_tostring(self):
        lims = Lims(self.url, username=self.username, password=self.password)
        from xml.etree import ElementTree as ET

        a = ET.Element("a")
        ET.SubElement(a, "b")
        c = ET.SubElement(a, "c")
        ET.SubElement(c, "d")
        etree = ET.ElementTree(a)
        expected_string = b"""<?xml version='1.0' encoding='utf-8'?>
<a><b /><c><d /></c></a>"""
        string = lims.tostring(etree)
        assert string == expected_string
