from unittest import mock

import pytest

from ligo.gracedb.utils import get_mimetype


class TestUploadReservedEventValidation:
    """Tests for upload_reserved_event error checking."""

    def test_wrong_api_version(self, safe_client):
        """Raises NotImplementedError when api_version is not v2."""
        with pytest.raises(NotImplementedError,
                           match="only implemented in API v2"):
            safe_client.upload_reserved_event('T123456', 'file.xml',
                                              filecontents=b'data')

    def test_missing_template(self, v2_client):
        """Raises NotImplementedError when server lacks the template."""
        mock_template_dict = {
            'event-detail-template': 'http://example.com'
        }
        template_prop = 'ligo.gracedb.rest.GraceDb.templates'

        with mock.patch(template_prop, mock_template_dict):
            with pytest.raises(NotImplementedError,
                               match="not yet implemented on"):
                v2_client.upload_reserved_event(
                    'T123456', 'file.xml', filecontents=b'data'
                )

    def test_templates_is_none(self, v2_client):
        """Raises NotImplementedError when templates is None."""
        template_prop = 'ligo.gracedb.rest.GraceDb.templates'

        with mock.patch(template_prop, None):
            with pytest.raises(NotImplementedError,
                               match="not yet implemented on"):
                v2_client.upload_reserved_event(
                    'T123456', 'file.xml', filecontents=b'data'
                )


class TestUploadReservedEventPost:
    """Tests for upload_reserved_event successful POST."""

    TEMPLATE = 'http://example.com/api/v2/events/{graceid}/upload/'
    TEMPLATE_PROP = 'ligo.gracedb.rest.GraceDb.templates'

    def _call(self, v2_client, **kwargs):
        """Call upload_reserved_event with mocked template and post."""
        mock_template_dict = {
            'event-upload-template': self.TEMPLATE
        }
        with mock.patch('ligo.gracedb.rest.GraceDb.post') \
                as mock_post, \
             mock.patch(self.TEMPLATE_PROP, mock_template_dict):
            v2_client.upload_reserved_event(**kwargs)
        return mock_post

    def test_post_with_filecontents(self, v2_client):
        """POST is called with graceid and eventFile."""
        filecontents = b'event data'
        filename = 'coinc.xml'
        file_mtype = get_mimetype(filename)

        mock_post = self._call(
            v2_client, graceid='T123456', filename=filename,
            filecontents=filecontents
        )

        mock_post.assert_called_once()
        call_args, call_kwargs = mock_post.call_args
        expected_uri = self.TEMPLATE.format(graceid='T123456')
        assert call_args == (expected_uri,)
        assert 'files' in call_kwargs
        assert call_kwargs['files'] == {
            'eventFile': (filename, filecontents, file_mtype)
        }

    def test_post_reads_file_when_no_filecontents(self, v2_client):
        """When filecontents is None, reads file from disk."""
        filename = 'coinc.xml'
        file_mtype = get_mimetype(filename)
        mock_template_dict = {
            'event-upload-template': self.TEMPLATE
        }
        open_func = 'ligo.gracedb.rest.open'
        open_mocker = mock.mock_open(read_data=b'file data')

        with mock.patch('ligo.gracedb.rest.GraceDb.post') \
                as mock_post, \
             mock.patch(self.TEMPLATE_PROP, mock_template_dict), \
             mock.patch(open_func, open_mocker):
            v2_client.upload_reserved_event(
                'T123456', filename
            )

        call_args, call_kwargs = mock_post.call_args
        assert call_kwargs['files'] == {
            'eventFile': (filename, open_mocker(), file_mtype)
        }

    def test_graceid_in_data(self, v2_client):
        """POST data includes graceid."""
        mock_post = self._call(
            v2_client, graceid='T123456', filename='coinc.xml',
            filecontents=b'data'
        )

        call_args, call_kwargs = mock_post.call_args
        body = dict(call_kwargs['data'])
        assert body['graceid'] == 'T123456'

    def test_labels_as_list(self, v2_client):
        """Labels list appears in POST data."""
        mock_post = self._call(
            v2_client, graceid='T123456', filename='coinc.xml',
            filecontents=b'data', labels=['DQV', 'INJ']
        )

        call_args, call_kwargs = mock_post.call_args
        label_values = [
            t[1] for t in call_kwargs['data'] if t[0] == 'labels'
        ]
        assert sorted(label_values) == ['DQV', 'INJ']

    def test_labels_as_string(self, v2_client):
        """Single label string is normalised to a list."""
        mock_post = self._call(
            v2_client, graceid='T123456', filename='coinc.xml',
            filecontents=b'data', labels='DQV'
        )

        call_args, call_kwargs = mock_post.call_args
        label_values = [
            t[1] for t in call_kwargs['data'] if t[0] == 'labels'
        ]
        assert label_values == ['DQV']

    def test_no_labels(self, v2_client):
        """Labels omitted from POST data when None."""
        mock_post = self._call(
            v2_client, graceid='T123456', filename='coinc.xml',
            filecontents=b'data'
        )

        call_args, call_kwargs = mock_post.call_args
        body_keys = [t[0] for t in call_kwargs['data']]
        assert 'labels' not in body_keys

    def test_kafka_metadata(self, v2_client):
        """kafka_server and kafka_topic appear in POST data."""
        mock_post = self._call(
            v2_client, graceid='T123456', filename='coinc.xml',
            filecontents=b'data',
            kafka_server='broker:9092', kafka_topic='cbc'
        )

        call_args, call_kwargs = mock_post.call_args
        body = dict(call_kwargs['data'])
        assert body['kafka_server'] == 'broker:9092'
        assert body['kafka_topic'] == 'cbc'

    def test_kafka_metadata_omitted_when_none(self, v2_client):
        """kafka_server and kafka_topic omitted when not provided."""
        mock_post = self._call(
            v2_client, graceid='T123456', filename='coinc.xml',
            filecontents=b'data'
        )

        call_args, call_kwargs = mock_post.call_args
        body_keys = [t[0] for t in call_kwargs['data']]
        assert 'kafka_server' not in body_keys
        assert 'kafka_topic' not in body_keys

    def test_returns_post_response(self, v2_client):
        """Returns the response from post()."""
        mock_template_dict = {
            'event-upload-template': self.TEMPLATE
        }
        with mock.patch('ligo.gracedb.rest.GraceDb.post') \
                as mock_post, \
             mock.patch(self.TEMPLATE_PROP, mock_template_dict):
            result = v2_client.upload_reserved_event(
                'T123456', 'coinc.xml', filecontents=b'data'
            )

        assert result == mock_post.return_value
