#   Copyright 2021 Huawei, Inc. All rights reserved.
#
#   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 unittest.mock import call

from osc_lib import exceptions

from openstackclient.network.v2 import local_ip_association
from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes_v2
from openstackclient.tests.unit.network.v2 import fakes as network_fakes


class TestLocalIPAssociation(network_fakes.TestNetworkV2):
    def setUp(self):
        super().setUp()
        self.network = self.app.client_manager.network
        self.local_ip = network_fakes.create_one_local_ip()
        self.fixed_port = network_fakes.create_one_port()
        self.project = identity_fakes_v2.FakeProject.create_one_project()
        self.network.find_port = mock.Mock(return_value=self.fixed_port)


class TestCreateLocalIPAssociation(TestLocalIPAssociation):
    def setUp(self):
        super().setUp()
        self.new_local_ip_association = (
            network_fakes.create_one_local_ip_association(
                attrs={
                    'fixed_port_id': self.fixed_port.id,
                    'local_ip_id': self.local_ip.id,
                }
            )
        )
        self.network.create_local_ip_association = mock.Mock(
            return_value=self.new_local_ip_association
        )

        self.network.find_local_ip = mock.Mock(return_value=self.local_ip)

        # Get the command object to test
        self.cmd = local_ip_association.CreateLocalIPAssociation(
            self.app, self.namespace
        )

        self.columns = (
            'local_ip_address',
            'fixed_port_id',
            'fixed_ip',
            'host',
        )

        self.data = (
            self.new_local_ip_association.local_ip_address,
            self.new_local_ip_association.fixed_port_id,
            self.new_local_ip_association.fixed_ip,
            self.new_local_ip_association.host,
        )

    def test_create_no_options(self):
        arglist = [
            self.new_local_ip_association.local_ip_id,
            self.new_local_ip_association.fixed_port_id,
        ]
        verifylist = [
            ('local_ip', self.new_local_ip_association.local_ip_id),
            ('fixed_port', self.new_local_ip_association.fixed_port_id),
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
        columns, data = self.cmd.take_action(parsed_args)

        self.network.create_local_ip_association.assert_called_once_with(
            self.new_local_ip_association.local_ip_id,
            **{
                'fixed_port_id': self.new_local_ip_association.fixed_port_id,
            }
        )
        self.assertEqual(set(self.columns), set(columns))
        self.assertEqual(set(self.data), set(data))

    def test_create_all_options(self):
        arglist = [
            self.new_local_ip_association.local_ip_id,
            self.new_local_ip_association.fixed_port_id,
            '--fixed-ip',
            self.new_local_ip_association.fixed_ip,
        ]
        verifylist = [
            ('local_ip', self.new_local_ip_association.local_ip_id),
            ('fixed_port', self.new_local_ip_association.fixed_port_id),
            ('fixed_ip', self.new_local_ip_association.fixed_ip),
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
        columns, data = self.cmd.take_action(parsed_args)

        self.network.create_local_ip_association.assert_called_once_with(
            self.new_local_ip_association.local_ip_id,
            **{
                'fixed_port_id': self.new_local_ip_association.fixed_port_id,
                'fixed_ip': self.new_local_ip_association.fixed_ip,
            }
        )
        self.assertEqual(set(self.columns), set(columns))
        self.assertEqual(set(self.data), set(data))


class TestDeleteLocalIPAssociation(TestLocalIPAssociation):
    def setUp(self):
        super().setUp()
        self._local_ip_association = (
            network_fakes.create_local_ip_associations(
                count=2,
                attrs={
                    'local_ip_id': self.local_ip.id,
                },
            )
        )
        self.network.delete_local_ip_association = mock.Mock(return_value=None)

        self.network.find_local_ip = mock.Mock(return_value=self.local_ip)
        # Get the command object to test
        self.cmd = local_ip_association.DeleteLocalIPAssociation(
            self.app, self.namespace
        )

    def test_local_ip_association_delete(self):
        arglist = [
            self.local_ip.id,
            self._local_ip_association[0].fixed_port_id,
        ]
        verifylist = [
            ('local_ip', self.local_ip.id),
            ('fixed_port_id', [self._local_ip_association[0].fixed_port_id]),
        ]

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

        result = self.cmd.take_action(parsed_args)

        self.network.delete_local_ip_association.assert_called_once_with(
            self.local_ip.id,
            self._local_ip_association[0].fixed_port_id,
            ignore_missing=False,
        )

        self.assertIsNone(result)

    def test_multi_local_ip_associations_delete(self):
        arglist = []
        fixed_port_id = []

        arglist.append(str(self.local_ip))

        for a in self._local_ip_association:
            arglist.append(a.fixed_port_id)
            fixed_port_id.append(a.fixed_port_id)

        verifylist = [
            ('local_ip', str(self.local_ip)),
            ('fixed_port_id', fixed_port_id),
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        result = self.cmd.take_action(parsed_args)

        calls = []
        for a in self._local_ip_association:
            calls.append(
                call(a.local_ip_id, a.fixed_port_id, ignore_missing=False)
            )

        self.network.delete_local_ip_association.assert_has_calls(calls)
        self.assertIsNone(result)

    def test_multi_local_ip_association_delete_with_exception(self):
        arglist = [
            self.local_ip.id,
            self._local_ip_association[0].fixed_port_id,
            'unexist_fixed_port_id',
        ]
        verifylist = [
            ('local_ip', self.local_ip.id),
            (
                'fixed_port_id',
                [
                    self._local_ip_association[0].fixed_port_id,
                    'unexist_fixed_port_id',
                ],
            ),
        ]
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)

        delete_mock_result = [None, exceptions.CommandError]

        self.network.delete_local_ip_association = mock.MagicMock(
            side_effect=delete_mock_result
        )

        try:
            self.cmd.take_action(parsed_args)
            self.fail('CommandError should be raised.')
        except exceptions.CommandError as e:
            self.assertEqual(
                '1 of 2 Local IP Associations failed to delete.', str(e)
            )

        self.network.delete_local_ip_association.assert_any_call(
            self.local_ip.id, 'unexist_fixed_port_id', ignore_missing=False
        )
        self.network.delete_local_ip_association.assert_any_call(
            self.local_ip.id,
            self._local_ip_association[0].fixed_port_id,
            ignore_missing=False,
        )


class TestListLocalIPAssociation(TestLocalIPAssociation):
    columns = (
        'Local IP ID',
        'Local IP Address',
        'Fixed port ID',
        'Fixed IP',
        'Host',
    )

    def setUp(self):
        super().setUp()
        self.local_ip_associations = (
            network_fakes.create_local_ip_associations(
                count=3,
                attrs={
                    'local_ip_id': self.local_ip.id,
                    'fixed_port_id': self.fixed_port.id,
                },
            )
        )
        self.data = []
        for lip_assoc in self.local_ip_associations:
            self.data.append(
                (
                    lip_assoc.local_ip_id,
                    lip_assoc.local_ip_address,
                    lip_assoc.fixed_port_id,
                    lip_assoc.fixed_ip,
                    lip_assoc.host,
                )
            )
        self.network.local_ip_associations = mock.Mock(
            return_value=self.local_ip_associations
        )
        self.network.find_local_ip = mock.Mock(return_value=self.local_ip)
        self.network.find_port = mock.Mock(return_value=self.fixed_port)
        # Get the command object to test
        self.cmd = local_ip_association.ListLocalIPAssociation(
            self.app, self.namespace
        )

    def test_local_ip_association_list(self):
        arglist = [self.local_ip.id]
        verifylist = [('local_ip', self.local_ip.id)]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
        columns, data = self.cmd.take_action(parsed_args)

        self.network.local_ip_associations.assert_called_once_with(
            self.local_ip, **{}
        )
        self.assertEqual(set(self.columns), set(columns))
        self.assertEqual(set(self.data), set(list(data)))

    def test_local_ip_association_list_all_options(self):
        arglist = [
            '--fixed-port',
            self.local_ip_associations[0].fixed_port_id,
            '--fixed-ip',
            self.local_ip_associations[0].fixed_ip,
            '--host',
            self.local_ip_associations[0].host,
            self.local_ip_associations[0].local_ip_id,
        ]

        verifylist = [
            ('fixed_port', self.local_ip_associations[0].fixed_port_id),
            ('fixed_ip', self.local_ip_associations[0].fixed_ip),
            ('host', self.local_ip_associations[0].host),
            ('local_ip', self.local_ip_associations[0].local_ip_id),
        ]

        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
        columns, data = self.cmd.take_action(parsed_args)

        attrs = {
            'fixed_port_id': self.local_ip_associations[0].fixed_port_id,
            'fixed_ip': self.local_ip_associations[0].fixed_ip,
            'host': self.local_ip_associations[0].host,
        }

        self.network.local_ip_associations.assert_called_once_with(
            self.local_ip, **attrs
        )
        self.assertEqual(set(self.columns), set(columns))
        self.assertEqual(set(self.data), set(list(data)))
