#  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 argparse
import functools

from cliff import command
from cliff.tests import base


class TestCommand(command.Command):
    """Description of command.
    """

    def get_parser(self, prog_name):
        parser = super(TestCommand, self).get_parser(prog_name)
        parser.add_argument(
            'long_help_argument',
            help="Create a NIC on the server.\n"
                 "Specify option multiple times to create multiple NICs. "
                 "Either net-id or port-id must be provided, but not both.\n"
                 "net-id: attach NIC to network with this UUID\n"
                 "port-id: attach NIC to port with this UUID\n"
                 "v4-fixed-ip: IPv4 fixed address for NIC (optional)\n"
                 "v6-fixed-ip: IPv6 fixed address for NIC (optional)\n"
                 "none: (v2.37+) no network is attached\n"
                 "auto: (v2.37+) the compute service will automatically "
                 "allocate a network.\n"
                 "Specifying a --nic of auto or none "
                 "cannot be used with any other --nic value.",
        )
        parser.add_argument(
            'regular_help_argument',
            help="The quick brown fox jumps "
                 "over the lazy dog.",
        )
        parser.add_argument(
            '-z',
            dest='zippy',
            default='zippy-default',
            help='defined in TestCommand and used in TestArgumentParser',
        )
        return parser

    def take_action(self, parsed_args):
        return 42


class TestCommandNoDocstring(command.Command):

    def take_action(self, parsed_args):
        return 42


class TestDescription(base.TestBase):

    def test_get_description_docstring(self):
        cmd = TestCommand(None, None)
        desc = cmd.get_description()
        assert desc == "Description of command.\n    "

    def test_get_description_attribute(self):
        cmd = TestCommand(None, None)
        # Artificially inject a value for _description to verify that it
        # overrides the docstring.
        cmd._description = 'this is not the default'
        desc = cmd.get_description()
        assert desc == 'this is not the default'

    def test_get_description_default(self):
        cmd = TestCommandNoDocstring(None, None)
        desc = cmd.get_description()
        assert desc == ''


class TestBasicValues(base.TestBase):

    def test_get_parser(self):
        cmd = TestCommand(None, None)
        parser = cmd.get_parser('NAME')
        assert parser.prog == 'NAME'

    def test_get_name(self):
        cmd = TestCommand(None, None, cmd_name='object action')
        assert cmd.cmd_name == 'object action'

    def test_run_return(self):
        cmd = TestCommand(None, None, cmd_name='object action')
        assert cmd.run(None) == 42


expected_help_message = """
  long_help_argument    Create a NIC on the server.
                        Specify option multiple times to create multiple NICs.
                        Either net-id or port-id must be provided, but not
                        both.
                        net-id: attach NIC to network with this UUID
                        port-id: attach NIC to port with this UUID
                        v4-fixed-ip: IPv4 fixed address for NIC (optional)
                        v6-fixed-ip: IPv6 fixed address for NIC (optional)
                        none: (v2.37+) no network is attached
                        auto: (v2.37+) the compute service will automatically
                        allocate a network.
                        Specifying a --nic of auto or none cannot be used with
                        any other --nic value.
  regular_help_argument
                        The quick brown fox jumps over the lazy dog.
"""


class TestHelp(base.TestBase):

    def test_smart_help_formatter(self):
        cmd = TestCommand(None, None)
        parser = cmd.get_parser('NAME')
        # Set up the formatter to always use a width=80 so that the
        # terminal width of the developer's system does not cause the
        # test to fail. Trying to mock os.environ failed, but there is
        # an arg to HelpFormatter to set the width
        # explicitly. Unfortunately, there is no way to do that
        # through the parser, so we have to replace the parser's
        # formatter_class attribute with a partial() that passes width
        # to the original class.
        parser.formatter_class = functools.partial(
            parser.formatter_class,
            width=78,
        )
        self.assertIn(expected_help_message, parser.format_help())


class TestArgumentParser(base.TestBase):

    def test_option_name_collision(self):
        cmd = TestCommand(None, None)
        parser = cmd.get_parser('NAME')
        # We should have an exception registering an option with a
        # name that already exists because we configure the argument
        # parser to ignore conflicts but this option has no other name
        # to be used.
        self.assertRaises(
            argparse.ArgumentError,
            parser.add_argument,
            '-z',
        )

    def test_option_name_collision_with_alias(self):
        cmd = TestCommand(None, None)
        parser = cmd.get_parser('NAME')
        # We not should have an exception registering an option with a
        # name that already exists because we configure the argument
        # parser to ignore conflicts and this option can be added as
        # --zero even if the -z is ignored.
        parser.add_argument('-z', '--zero')

    def test_resolve_option_with_name_collision(self):
        cmd = TestCommand(None, None)
        parser = cmd.get_parser('NAME')
        parser.add_argument(
            '-z', '--zero',
            dest='zero',
            default='zero-default',
        )
        args = parser.parse_args(['-z', 'foo', 'a', 'b'])
        self.assertEqual(args.zippy, 'foo')
        self.assertEqual(args.zero, 'zero-default')

    def test_with_conflict_handler(self):
        cmd = TestCommand(None, None)
        cmd.conflict_handler = 'resolve'
        parser = cmd.get_parser('NAME')
        self.assertEqual(parser.conflict_handler, 'resolve')

    def test_raise_conflict_argument_error(self):
        cmd = TestCommand(None, None)
        parser = cmd.get_parser('NAME')
        parser.add_argument(
            '-f', '--foo',
            dest='foo',
            default='foo',
        )
        self.assertRaises(
            argparse.ArgumentError,
            parser.add_argument,
            '-f',
        )

    def test_resolve_conflict_argument(self):
        cmd = TestCommand(None, None)
        cmd.conflict_handler = 'resolve'
        parser = cmd.get_parser('NAME')
        parser.add_argument(
            '-f', '--foo',
            dest='foo',
            default='foo',
        )
        parser.add_argument(
            '-f', '--foo',
            dest='foo',
            default='bar',
        )
        args = parser.parse_args(['a', 'b'])
        self.assertEqual(args.foo, 'bar')

    def test_wrong_conflict_handler(self):
        cmd = TestCommand(None, None)
        cmd.conflict_handler = 'wrong'
        self.assertRaises(
            ValueError,
            cmd.get_parser,
            'NAME',
        )
