#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
from unittest import mock

import fixtures
from keystoneclient import discover as ks_discover
from oslo_config import cfg
from oslo_config import fixture as config_fixture
import testtools
from testtools import matchers
from zaqarclient.queues.v2 import message
from zaqarclient import transport
from zaqarclient.transport import response

from os_collect_config import collect
from os_collect_config import exc
from os_collect_config.tests import test_heat
from os_collect_config import zaqar


class FakeKeystoneClient(test_heat.FakeKeystoneClient):

    def url_for(self, service_type, endpoint_type):
        self._test.assertEqual('messaging', service_type)
        self._test.assertEqual('publicURL', endpoint_type)
        return 'http://192.0.2.1:8888/'


class FakeKeystoneClientWebsocket(test_heat.FakeKeystoneClient):

    def url_for(self, service_type, endpoint_type):
        self._test.assertEqual('messaging-websocket', service_type)
        self._test.assertEqual('publicURL', endpoint_type)
        return 'ws://127.0.0.1:9000/'


class FakeZaqarClient:

    def __init__(self, testcase):
        self._test = testcase

    def Client(self, endpoint, conf, version):
        self._test.assertEqual(2, version)
        self._test.assertEqual('http://192.0.2.1:8888/', endpoint)
        return self

    def queue(self, queue_id):
        self._test.assertEqual(
            '4f3f46d3-09f1-42a7-8c13-f91a5457192c', queue_id)
        return FakeQueue()


class FakeZaqarWebsocketClient:

    def __init__(self, options, messages=None, testcase=None):
        self._messages = messages
        self._test = testcase

    def send(self, request):
        self._test.assertEqual('ws://127.0.0.1:9000/', request.endpoint)
        if request.operation == 'message_list':
            body = json.loads(request.content)
            self._test.assertEqual(
                '4f3f46d3-09f1-42a7-8c13-f91a5457192c', body['queue_name'])
        return response.Response(request, content=json.dumps(self._messages),
                                 status_code=200)

    def recv(self):
        return {'body': test_heat.META_DATA}

    def __enter__(self):
        return self

    def __exit__(self, *exc):
        pass


class FakeQueue:

    def pop(self):
        return iter([message.Message(
            queue=self, ttl=10, age=10, body=test_heat.META_DATA, href='')])


class FakeZaqarClientSoftwareConfig:

    def __init__(self, testcase):
        self._test = testcase

    def Client(self, endpoint, conf, version):
        self._test.assertEqual(2, version)
        self._test.assertEqual('http://192.0.2.1:8888/', endpoint)
        return self

    def queue(self, queue_id):
        self._test.assertEqual(
            '4f3f46d3-09f1-42a7-8c13-f91a5457192c', queue_id)
        return FakeQueueSoftwareConfig()


class FakeQueueSoftwareConfig:

    def pop(self):
        return iter([message.Message(
            queue=self, ttl=10, age=10, body=test_heat.SOFTWARE_CONFIG_DATA,
            href='')])


class TestZaqar(testtools.TestCase):
    def setUp(self):
        super().setUp()
        self.log = self.useFixture(fixtures.FakeLogger())
        self.useFixture(fixtures.NestedTempfile())
        collect.setup_conf()

        conf = config_fixture.Config()
        self.useFixture(conf)
        conf.config(group='zaqar', use_websockets=False)
        conf.config(group='zaqar', auth_url='http://192.0.2.1:5000/v3')
        conf.config(group='zaqar', user_id='0123456789ABCDEF')
        conf.config(group='zaqar', password='FEDCBA9876543210')
        conf.config(group='zaqar',
                    project_id='9f6b09df-4d7f-4a33-8ec3-9924d8f46f10')
        conf.config(group='zaqar',
                    queue_id='4f3f46d3-09f1-42a7-8c13-f91a5457192c')
        conf.config(group='zaqar', ssl_certificate_validation=True)
        conf.config(group='zaqar', ca_file='/foo/bar')

    @mock.patch.object(ks_discover.Discover, '__init__')
    @mock.patch.object(ks_discover.Discover, 'url_for')
    def test_collect_zaqar(self, mock_url_for, mock___init__):
        mock___init__.return_value = None
        mock_url_for.return_value = cfg.CONF.zaqar.auth_url
        zaqar_md = zaqar.Collector(
            keystoneclient=FakeKeystoneClient(self, cfg.CONF.zaqar),
            zaqarclient=FakeZaqarClient(self),
            discover_class=test_heat.FakeKeystoneDiscover).collect()
        self.assertThat(zaqar_md, matchers.IsInstance(list))
        self.assertEqual('zaqar', zaqar_md[0][0])
        zaqar_md = zaqar_md[0][1]

        for k in ('int1', 'strfoo', 'map_ab'):
            self.assertIn(k, zaqar_md)
            self.assertEqual(zaqar_md[k], test_heat.META_DATA[k])

    @mock.patch.object(ks_discover.Discover, '__init__')
    @mock.patch.object(ks_discover.Discover, 'url_for')
    def test_collect_zaqar_deployments(self, mock_url_for, mock___init__):
        mock___init__.return_value = None
        mock_url_for.return_value = cfg.CONF.zaqar.auth_url
        zaqar_md = zaqar.Collector(
            keystoneclient=FakeKeystoneClient(self, cfg.CONF.zaqar),
            zaqarclient=FakeZaqarClientSoftwareConfig(self),
            discover_class=test_heat.FakeKeystoneDiscover).collect()
        self.assertThat(zaqar_md, matchers.IsInstance(list))
        self.assertEqual('zaqar', zaqar_md[0][0])
        self.assertEqual(2, len(zaqar_md))
        self.assertEqual('zaqar', zaqar_md[0][0])
        self.assertEqual(
            test_heat.SOFTWARE_CONFIG_DATA['deployments'],
            zaqar_md[0][1]['deployments'])
        self.assertEqual(
            ('dep-name1', {'config1': 'value1'}), zaqar_md[1])

    @mock.patch.object(ks_discover.Discover, '__init__')
    @mock.patch.object(ks_discover.Discover, 'url_for')
    def test_collect_zaqar_fail(self, mock_url_for, mock___init__):
        mock___init__.return_value = None
        mock_url_for.return_value = cfg.CONF.zaqar.auth_url
        zaqar_collect = zaqar.Collector(
            keystoneclient=test_heat.FakeFailKeystoneClient(
                self, cfg.CONF.zaqar),
            zaqarclient=FakeZaqarClient(self),
            discover_class=test_heat.FakeKeystoneDiscover)
        self.assertRaises(exc.ZaqarMetadataNotAvailable, zaqar_collect.collect)
        self.assertIn('Forbidden', self.log.output)

    def test_collect_zaqar_no_auth_url(self):
        cfg.CONF.zaqar.auth_url = None
        zaqar_collect = zaqar.Collector()
        self.assertRaises(
            exc.ZaqarMetadataNotConfigured, zaqar_collect.collect)
        self.assertIn('No auth_url configured', self.log.output)

    def test_collect_zaqar_no_password(self):
        cfg.CONF.zaqar.password = None
        zaqar_collect = zaqar.Collector()
        self.assertRaises(
            exc.ZaqarMetadataNotConfigured, zaqar_collect.collect)
        self.assertIn('No password configured', self.log.output)

    def test_collect_zaqar_no_project_id(self):
        cfg.CONF.zaqar.project_id = None
        zaqar_collect = zaqar.Collector()
        self.assertRaises(
            exc.ZaqarMetadataNotConfigured, zaqar_collect.collect)
        self.assertIn('No project_id configured', self.log.output)

    def test_collect_zaqar_no_user_id(self):
        cfg.CONF.zaqar.user_id = None
        zaqar_collect = zaqar.Collector()
        self.assertRaises(
            exc.ZaqarMetadataNotConfigured, zaqar_collect.collect)
        self.assertIn('No user_id configured', self.log.output)

    def test_collect_zaqar_no_queue_id(self):
        cfg.CONF.zaqar.queue_id = None
        zaqar_collect = zaqar.Collector()
        self.assertRaises(
            exc.ZaqarMetadataNotConfigured, zaqar_collect.collect)
        self.assertIn('No queue_id configured', self.log.output)

    def test_collect_zaqar_no_ca_file(self):
        cfg.CONF.zaqar.ssl_certificate_validation = True
        cfg.CONF.zaqar.ca_file = None
        zaqar_collect = zaqar.Collector()
        self.assertRaises(
            exc.ZaqarMetadataNotConfigured, zaqar_collect.collect)
        expected = ('No CA file configured when flag ssl certificate '
                    'validation is on.')
        self.assertIn(expected, self.log.output)

    @mock.patch.object(transport, 'get_transport_for')
    @mock.patch.object(ks_discover.Discover, '__init__')
    @mock.patch.object(ks_discover.Discover, 'url_for')
    def test_collect_zaqar_websocket(self, mock_url_for, mock___init__,
                                     mock_transport):

        mock___init__.return_value = None
        mock_url_for.return_value = cfg.CONF.zaqar.auth_url
        conf = config_fixture.Config()
        self.useFixture(conf)
        conf.config(group='zaqar', use_websockets=True)
        messages = {'messages': [{'body': test_heat.META_DATA, 'id': 1}]}
        ws = FakeZaqarWebsocketClient({}, messages=messages, testcase=self)
        mock_transport.return_value = ws
        zaqar_md = zaqar.Collector(
            keystoneclient=FakeKeystoneClientWebsocket(self, cfg.CONF.zaqar)
        ).collect()
        self.assertThat(zaqar_md, matchers.IsInstance(list))
        self.assertEqual('zaqar', zaqar_md[0][0])
        zaqar_md = zaqar_md[0][1]

        for k in ('int1', 'strfoo', 'map_ab'):
            self.assertIn(k, zaqar_md)
            self.assertEqual(zaqar_md[k], test_heat.META_DATA[k])

    @mock.patch.object(transport, 'get_transport_for')
    @mock.patch.object(ks_discover.Discover, '__init__')
    @mock.patch.object(ks_discover.Discover, 'url_for')
    def test_collect_zaqar_websocket_recv(self, mock_url_for, mock___init__,
                                          mock_transport):
        mock___init__.return_value = None
        mock_url_for.return_value = cfg.CONF.zaqar.auth_url
        ws = FakeZaqarWebsocketClient({}, messages={}, testcase=self)
        mock_transport.return_value = ws
        conf = config_fixture.Config()
        self.useFixture(conf)
        conf.config(group='zaqar', use_websockets=True)
        zaqar_md = zaqar.Collector(
            keystoneclient=FakeKeystoneClientWebsocket(self, cfg.CONF.zaqar),
        ).collect()
        self.assertThat(zaqar_md, matchers.IsInstance(list))
        self.assertEqual('zaqar', zaqar_md[0][0])
        zaqar_md = zaqar_md[0][1]

        for k in ('int1', 'strfoo', 'map_ab'):
            self.assertIn(k, zaqar_md)
            self.assertEqual(zaqar_md[k], test_heat.META_DATA[k])
