#   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.
#
from unittest import mock

from osc_lib import exceptions
from osc_lib import utils as oscutils

from manilaclient import api_versions
from manilaclient.common import cliutils

from manilaclient.osc import utils
from manilaclient.osc.v2 import share_replicas as osc_share_replicas

from manilaclient.tests.unit.osc import osc_utils
from manilaclient.tests.unit.osc.v2 import fakes as manila_fakes


class TestShareReplica(manila_fakes.TestShare):

    def setUp(self):
        super(TestShareReplica, self).setUp()

        self.shares_mock = self.app.client_manager.share.shares
        self.shares_mock.reset_mock()

        self.replicas_mock = self.app.client_manager.share.share_replicas
        self.replicas_mock.reset_mock()
        self.app.client_manager.share.api_version = api_versions.APIVersion(
            api_versions.MAX_VERSION)

        self.replica_el_mock = (
            self.app.client_manager
                .share.share_replica_export_locations)
        self.replica_el_mock.reset_mock()


class TestShareReplicaCreate(TestShareReplica):

    def setUp(self):
        super(TestShareReplicaCreate, self).setUp()

        self.share = manila_fakes.FakeShare.create_one_share()
        self.shares_mock.get.return_value = self.share

        self.share_replica = (
            manila_fakes.FakeShareReplica.create_one_replica(
                attrs={
                    'availability_zone': 'manila-zone-1',
                    'status': 'available'}
            ))
        self.replicas_mock.create.return_value = self.share_replica
        self.replicas_mock.get.return_value = self.share_replica

        self.cmd = osc_share_replicas.CreateShareReplica(self.app, None)

        self.data = tuple(self.share_replica._info.values())
        self.columns = tuple(self.share_replica._info.keys())

    def test_share_replica_create_missing_args(self):
        arglist = []
        verifylist = []

        self.assertRaises(
            osc_utils.ParserException,
            self.check_parser, self.cmd, arglist, verifylist)

    def test_share_replica_create(self):
        arglist = [
            self.share.id
        ]
        verifylist = [
            ('share', self.share.id)
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        columns, data = self.cmd.take_action(parsed_args)

        self.replicas_mock.create.assert_called_with(
            share=self.share,
            availability_zone=None
        )

        self.assertCountEqual(self.columns, columns)
        self.assertCountEqual(self.data, data)

    def test_share_replica_create_az(self):
        arglist = [
            self.share.id,
            '--availability-zone', self.share.availability_zone
        ]
        verifylist = [
            ('share', self.share.id),
            ('availability_zone', self.share.availability_zone)
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        columns, data = self.cmd.take_action(parsed_args)

        self.replicas_mock.create.assert_called_with(
            share=self.share,
            availability_zone=self.share.availability_zone
        )

        self.assertCountEqual(self.columns, columns)
        self.assertCountEqual(self.data, data)

    def test_share_replica_create_scheduler_hint_valid(self):
        arglist = [
            self.share.id,
            '--availability-zone', self.share.availability_zone,
            '--scheduler-hint', ('only_host=host1@backend1#pool1'),
        ]
        verifylist = [
            ('share', self.share.id),
            ('availability_zone', self.share.availability_zone),
            ('scheduler_hint', {'only_host': 'host1@backend1#pool1'})
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        columns, data = self.cmd.take_action(parsed_args)

        self.replicas_mock.create.assert_called_with(
            share=self.share,
            availability_zone=self.share.availability_zone,
            scheduler_hints={'only_host': 'host1@backend1#pool1'}
        )

        self.assertCountEqual(self.columns, columns)
        self.assertCountEqual(self.data, data)

    def test_share_replica_create_scheduler_hint_invalid_hint(self):
        arglist = [
            self.share.id,
            '--availability-zone', self.share.availability_zone,
            '--scheduler-hint', 'fake_hint=host1@backend1#pool1'
        ]
        verifylist = [
            ('share', self.share.id),
            ('availability_zone', self.share.availability_zone),
            ('scheduler_hint', {'fake_hint': 'host1@backend1#pool1'})
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
        self.assertRaises(exceptions.CommandError,
                          self.cmd.take_action,
                          parsed_args)

    def test_share_replica_create_scheduler_hint_invalid_version(self):
        self.app.client_manager.share.api_version = api_versions.APIVersion(
            "2.66")

        arglist = [
            self.share.id,
            '--availability-zone', self.share.availability_zone,
            '--scheduler-hint', 'only_host=host1@backend1#pool1'
        ]
        verifylist = [
            ('share', self.share.id),
            ('availability_zone', self.share.availability_zone),
            ('scheduler_hint', {'only_host': 'host1@backend1#pool1'})
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
        self.assertRaises(exceptions.CommandError,
                          self.cmd.take_action,
                          parsed_args)

    def test_share_replica_create_share_network(self):
        self.app.client_manager.share.api_version = api_versions.APIVersion(
            "2.72")

        arglist = [
            self.share.id,
            '--availability-zone', self.share.availability_zone,
            '--share-network', self.share.share_network_id
        ]
        verifylist = [
            ('share', self.share.id),
            ('availability_zone', self.share.availability_zone),
            ('share_network', self.share.share_network_id)
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        columns, data = self.cmd.take_action(parsed_args)
        if self.share.share_network_id:
            self.replicas_mock.create.assert_called_with(
                share=self.share,
                availability_zone=self.share.availability_zone,
                share_network=self.share.share_network_id
            )
        else:
            self.replicas_mock.create.assert_called_with(
                share=self.share,
                availability_zone=self.share.availability_zone,
            )

        self.assertCountEqual(self.columns, columns)
        self.assertCountEqual(self.data, data)

    def test_share_replica_create_wait(self):
        arglist = [
            self.share.id,
            '--wait'
        ]
        verifylist = [
            ('share', self.share.id),
            ('wait', True)
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        columns, data = self.cmd.take_action(parsed_args)

        self.replicas_mock.create.assert_called_with(
            share=self.share,
            availability_zone=None
        )

        self.replicas_mock.get.assert_called_with(self.share_replica.id)
        self.assertCountEqual(self.columns, columns)
        self.assertCountEqual(self.data, data)

    @mock.patch('manilaclient.osc.v2.share_replicas.LOG')
    def test_share_replica_create_wait_exception(self, mock_logger):
        arglist = [
            self.share.id,
            '--wait'
        ]
        verifylist = [
            ('share', self.share.id),
            ('wait', True)
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        with mock.patch('osc_lib.utils.wait_for_status', return_value=False):
            columns, data = self.cmd.take_action(parsed_args)

            self.replicas_mock.create.assert_called_with(
                share=self.share,
                availability_zone=None
            )

            mock_logger.error.assert_called_with(
                "ERROR: Share replica is in error state.")

            self.replicas_mock.get.assert_called_with(self.share_replica.id)
            self.assertCountEqual(self.columns, columns)
            self.assertCountEqual(self.data, data)


class TestShareReplicaDelete(TestShareReplica):

    def setUp(self):
        super(TestShareReplicaDelete, self).setUp()

        self.share_replica = (
            manila_fakes.FakeShareReplica.create_one_replica())
        self.replicas_mock.get.return_value = self.share_replica

        self.cmd = osc_share_replicas.DeleteShareReplica(self.app, None)

    def test_share_replica_delete_missing_args(self):
        arglist = []
        verifylist = []

        self.assertRaises(osc_utils.ParserException,
                          self.check_parser, self.cmd, arglist, verifylist)

    def test_share_replica_delete(self):
        arglist = [
            self.share_replica.id
        ]
        verifylist = [
            ('replica', [self.share_replica.id])
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        result = self.cmd.take_action(parsed_args)

        self.replicas_mock.delete.assert_called_with(
            self.share_replica,
            force=False)
        self.assertIsNone(result)

    def test_share_replica_delete_force(self):
        arglist = [
            self.share_replica.id,
            '--force'
        ]
        verifylist = [
            ('replica', [self.share_replica.id]),
            ('force', True)
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        result = self.cmd.take_action(parsed_args)

        self.replicas_mock.delete.assert_called_with(
            self.share_replica,
            force=True)
        self.assertIsNone(result)

    def test_share_replica_delete_multiple(self):
        share_replicas = (
            manila_fakes.FakeShareReplica.create_share_replicas(
                count=2))
        arglist = [
            share_replicas[0].id,
            share_replicas[1].id
        ]
        verifylist = [
            ('replica', [share_replicas[0].id, share_replicas[1].id])
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        result = self.cmd.take_action(parsed_args)

        self.assertEqual(self.replicas_mock.delete.call_count,
                         len(share_replicas))
        self.assertIsNone(result)

    def test_share_snapshot_delete_exception(self):
        arglist = [
            self.share_replica.id
        ]
        verifylist = [
            ('replica', [self.share_replica.id])
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        self.replicas_mock.delete.side_effect = exceptions.CommandError()
        self.assertRaises(exceptions.CommandError,
                          self.cmd.take_action,
                          parsed_args)

    def test_share_replica_delete_wait(self):
        arglist = [
            self.share_replica.id,
            '--wait'
        ]
        verifylist = [
            ('replica', [self.share_replica.id]),
            ('wait', True)
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        with mock.patch('osc_lib.utils.wait_for_delete', return_value=True):
            result = self.cmd.take_action(parsed_args)

            self.replicas_mock.delete.assert_called_with(
                self.share_replica,
                force=False)
            self.replicas_mock.get.assert_called_with(self.share_replica.id)
            self.assertIsNone(result)

    def test_share_replica_delete_wait_exception(self):
        arglist = [
            self.share_replica.id,
            '--wait'
        ]
        verifylist = [
            ('replica', [self.share_replica.id]),
            ('wait', True)
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        with mock.patch('osc_lib.utils.wait_for_delete', return_value=False):
            self.assertRaises(
                exceptions.CommandError,
                self.cmd.take_action,
                parsed_args
            )


class TestShareReplicaList(TestShareReplica):

    columns = [
        'id',
        'status',
        'replica_state',
        'share_id',
        'host',
        'availability_zone',
        'updated_at',
    ]

    column_headers = utils.format_column_headers(columns)

    def setUp(self):
        super(TestShareReplicaList, self).setUp()

        self.share = manila_fakes.FakeShare.create_one_share()
        self.shares_mock.get.return_value = self.share

        self.replicas_list = (
            manila_fakes.FakeShareReplica.create_share_replicas(
                count=2))
        self.replicas_mock.list.return_value = self.replicas_list

        self.values = (oscutils.get_dict_properties(
            i._info, self.columns) for i in self.replicas_list)

        self.cmd = osc_share_replicas.ListShareReplica(self.app, None)

    def test_share_replica_list(self):
        arglist = []
        verifylist = []

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        columns, data = self.cmd.take_action(parsed_args)

        self.replicas_mock.list.assert_called_with(share=None)

        self.assertEqual(self.column_headers, columns)
        self.assertEqual(list(self.values), list(data))

    def test_share_replica_list_for_share(self):
        arglist = [
            '--share', self.share.id
        ]
        verifylist = [
            ('share', self.share.id)
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        columns, data = self.cmd.take_action(parsed_args)

        self.replicas_mock.list.assert_called_with(share=self.share)

        self.assertEqual(self.column_headers, columns)
        self.assertEqual(list(self.values), list(data))


class TestShareReplicaShow(TestShareReplica):

    def setUp(self):
        super(TestShareReplicaShow, self).setUp()

        self.share_replica = (
            manila_fakes.FakeShareReplica.create_one_replica()
        )
        self.replicas_mock.get.return_value = self.share_replica

        self.replica_el_list = (
            manila_fakes.FakeShareExportLocation.
            create_share_export_locations(count=2)
        )

        self.replica_el_mock.list.return_value = (
            self.replica_el_list)

        self.cmd = osc_share_replicas.ShowShareReplica(self.app, None)

        self.share_replica._info['export_locations'] = (
            cliutils.convert_dict_list_to_string(
                self.replica_el_list))

        self.data = tuple(self.share_replica._info.values())
        self.columns = tuple(self.share_replica._info.keys())

    def test_share_replica_show_missing_args(self):
        arglist = []
        verifylist = []

        self.assertRaises(
            osc_utils.ParserException,
            self.check_parser, self.cmd, arglist, verifylist)

    def test_share_replica_show(self):
        arglist = [
            self.share_replica.id
        ]
        verifylist = [
            ('replica', self.share_replica.id)
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        columns, data = self.cmd.take_action(parsed_args)

        self.replicas_mock.get.assert_called_with(
            self.share_replica.id
        )

        self.assertCountEqual(self.columns, columns)
        self.assertCountEqual(self.data, data)


class TestShareReplicaSet(TestShareReplica):

    def setUp(self):
        super(TestShareReplicaSet, self).setUp()

        self.share_replica = (
            manila_fakes.FakeShareReplica.create_one_replica()
        )
        self.replicas_mock.get.return_value = self.share_replica

        self.cmd = osc_share_replicas.SetShareReplica(self.app, None)

    def test_share_replica_set_replica_state(self):
        new_replica_state = 'in_sync'
        arglist = [
            self.share_replica.id,
            '--replica-state', new_replica_state
        ]
        verifylist = [
            ('replica', self.share_replica.id),
            ('replica_state', new_replica_state)
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        result = self.cmd.take_action(parsed_args)

        self.replicas_mock.reset_replica_state.assert_called_with(
            self.share_replica,
            new_replica_state)
        self.assertIsNone(result)

    def test_share_replica_set_replica_state_exception(self):
        new_replica_state = 'in_sync'
        arglist = [
            self.share_replica.id,
            '--replica-state', new_replica_state
        ]
        verifylist = [
            ('replica', self.share_replica.id),
            ('replica_state', new_replica_state)
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        self.replicas_mock.reset_replica_state.side_effect = Exception()

        self.assertRaises(
            exceptions.CommandError,
            self.cmd.take_action,
            parsed_args)

    def test_share_replica_set_status(self):
        new_status = 'available'
        arglist = [
            self.share_replica.id,
            '--status', new_status
        ]
        verifylist = [
            ('replica', self.share_replica.id),
            ('status', new_status)
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        result = self.cmd.take_action(parsed_args)

        self.replicas_mock.reset_state.assert_called_with(
            self.share_replica,
            new_status)
        self.assertIsNone(result)

    def test_share_replica_set_status_exception(self):
        new_status = 'available'
        arglist = [
            self.share_replica.id,
            '--status', new_status
        ]
        verifylist = [
            ('replica', self.share_replica.id),
            ('status', new_status)
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        self.replicas_mock.reset_state.side_effect = Exception()

        self.assertRaises(
            exceptions.CommandError,
            self.cmd.take_action,
            parsed_args)

    def test_share_replica_set_nothing_defined(self):
        arglist = [
            self.share_replica.id,
        ]
        verifylist = [
            ('replica', self.share_replica.id),
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        self.assertRaises(
            exceptions.CommandError,
            self.cmd.take_action,
            parsed_args)


class TestShareReplicaPromote(TestShareReplica):

    def setUp(self):
        super(TestShareReplicaPromote, self).setUp()

        self.share_replica = (
            manila_fakes.FakeShareReplica.create_one_replica(
                attrs={
                    'status': 'available'}
            )
        )
        self.replicas_mock.get.return_value = self.share_replica

        self.cmd = osc_share_replicas.PromoteShareReplica(
            self.app, None)

    def test_share_replica_promote(self):
        arglist = [
            self.share_replica.id,
        ]
        verifylist = [
            ('replica', self.share_replica.id),
            ('wait', False)
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        result = self.cmd.take_action(parsed_args)

        self.replicas_mock.promote.assert_called_with(
            self.share_replica)
        self.assertIsNone(result)

    def test_share_replica_promote_quiesce_wait_time(self):
        wait_time = '5'
        arglist = [
            self.share_replica.id,
            '--quiesce-wait-time', wait_time
        ]
        verifylist = [
            ('replica', self.share_replica.id),
            ('quiesce_wait_time', wait_time)
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        result = self.cmd.take_action(parsed_args)

        self.replicas_mock.promote.assert_called_with(
            self.share_replica,
            wait_time)
        self.assertIsNone(result)

    @mock.patch.object(osc_share_replicas.osc_utils, 'wait_for_status',
                       mock.Mock())
    def test_share_replica_promote_wait(self):
        arglist = [
            self.share_replica.id,
            '--wait'
        ]
        verifylist = [
            ('replica', self.share_replica.id),
            ('wait', True)
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        result = self.cmd.take_action(parsed_args)

        self.replicas_mock.promote.assert_called_with(
            self.share_replica)
        self.assertIsNone(result)
        osc_share_replicas.osc_utils.wait_for_status.assert_called_once_with(
            status_f=self.replicas_mock.get, res_id=self.share_replica.id,
            success_status=['active'], status_field='replica_state')

    def test_share_replica_promote_exception(self):
        arglist = [
            self.share_replica.id,
        ]
        verifylist = [
            ('replica', self.share_replica.id),
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        self.replicas_mock.promote.side_effect = Exception()

        self.assertRaises(
            exceptions.CommandError,
            self.cmd.take_action,
            parsed_args)


class TestShareReplicaResync(TestShareReplica):

    def setUp(self):
        super(TestShareReplicaResync, self).setUp()

        self.share_replica = (
            manila_fakes.FakeShareReplica.create_one_replica()
        )
        self.replicas_mock.get.return_value = self.share_replica

        self.cmd = osc_share_replicas.ResyncShareReplica(
            self.app, None)

    def test_share_replica_resync(self):
        arglist = [
            self.share_replica.id,
        ]
        verifylist = [
            ('replica', self.share_replica.id),
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        result = self.cmd.take_action(parsed_args)

        self.replicas_mock.resync.assert_called_with(
            self.share_replica)
        self.assertIsNone(result)

    def test_share_replica_resync_exception(self):
        arglist = [
            self.share_replica.id,
        ]
        verifylist = [
            ('replica', self.share_replica.id),
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        self.replicas_mock.resync.side_effect = Exception()

        self.assertRaises(
            exceptions.CommandError,
            self.cmd.take_action,
            parsed_args)
