import argparse
import unittest
from unittest.mock import Mock, patch

import tufup
import tufup.repo.cli
import tufup.utils
from tests import TempDirTestCase


class ParserTests(unittest.TestCase):
    def test_get_parser(self):
        parser = tufup.repo.cli.get_parser()
        for cmd in [
            'init',
            'init --debug',
            'targets add 1.0 c:\\my_bundle_dir c:\\private_keys',
            'targets -d add 1.0 c:\\my_bundle_dir c:\\private_keys',
            'targets -d add -s 1.0 c:\\my_bundle_dir c:\\private_keys',
            'targets remove-latest c:\\private_keys',
            'keys my-key-name -c -e',
            'keys my-key-name add root c:\\private_keys d:\\more_private_keys',
            'keys my-key-name -c -e add root c:\\private_keys',
            'keys my-key-name replace old-key-name c:\\private_keys',
            'keys my-key-name -c -e replace old-key-name c:\\private_keys',
            'sign root c:\\private_keys d:\\other_private_keys',
            'sign root c:\\private_keys -e',
            'sign root c:\\private_keys -e 100',
        ]:
            with self.subTest(msg=cmd):
                args = cmd.split()
                options = parser.parse_args(args)
                expected_func_name = '_cmd_' + args[0]
                self.assertEqual(expected_func_name, options.func.__name__)


class CommandTests(TempDirTestCase):
    def setUp(self) -> None:
        super().setUp()
        role_names = ['root', 'targets', 'snapshot', 'timestamp']
        self.config = dict(
            app_name='my-app',
            repo_dir=self.temp_dir_path / 'repo',
            keys_dir=self.temp_dir_path / 'keys',
            key_map={name: [name] for name in role_names},
            encrypted_keys=[],
            expiration_days={name: 1 for name in role_names},
        )
        mock_return_config = Mock(return_value=self.config)
        mock_keys = Mock()
        mock_keys.create_key_pair = Mock()
        mock_repo = Mock(keys=mock_keys, **self.config)
        mock_repo.save_config = Mock()
        mock_repo.initialize = Mock()
        mock_repo.add_bundle = Mock()
        mock_repo.remove_latest_bundle = Mock()
        mock_repo.add_key = Mock()
        mock_repo.replace_key = Mock()
        mock_repo.refresh_expiration_date = Mock()
        mock_repo.threshold_sign = Mock()
        mock_repo.publish_changes = Mock()
        MockRepository = Mock(return_value=mock_repo)  # noqa
        MockRepository.load_config = mock_return_config
        MockRepository.from_config = Mock(return_value=mock_repo)
        self.mock_repo = mock_repo
        self.mock_repo_class = MockRepository

    def test__cmd_init(self):
        with patch('tufup.repo.cli.Repository', self.mock_repo_class):
            with patch('tufup.repo.cli.input_bool', Mock(return_value=True)):
                with patch(
                        'tufup.repo.cli._get_config_from_user',
                        self.mock_repo_class.load_config,
                ):
                    tufup.repo.cli._cmd_init(options=argparse.Namespace())
        self.mock_repo.initialize.assert_called()

    def test__cmd_keys_create(self):
        options = argparse.Namespace(
            new_key_name='test', encrypted=True, create=True
        )
        with patch('tufup.repo.cli.Repository', self.mock_repo_class):
            tufup.repo.cli._cmd_keys(options=options)
        self.mock_repo.keys.create_key_pair.assert_called()

    def test__cmd_keys_create_and_add_key(self):
        options = argparse.Namespace(
            create=True,
            encrypted=True,
            key_dirs=['c:\\my_private_keys'],
            new_key_name='test',
            role_name='root',
        )
        with patch('tufup.repo.cli.Repository', self.mock_repo_class):
            tufup.repo.cli._cmd_keys(options=options)
        self.mock_repo.keys.create_key_pair.assert_called()
        self.mock_repo.add_key.assert_called()
        self.mock_repo.publish_changes.assert_called()

    def test__cmd_keys_replace_key(self):
        options = argparse.Namespace(
            create=True,
            encrypted=False,
            key_dirs=['c:\\my_private_keys'],
            new_key_name='some new key to be created',
            old_key_name='some old key name',
        )
        with patch('tufup.repo.cli.Repository', self.mock_repo_class):
            tufup.repo.cli._cmd_keys(options=options)
        self.mock_repo.replace_key.assert_called()
        self.mock_repo.keys.create_key_pair.assert_called()
        self.mock_repo.publish_changes.assert_called()

    def test__cmd_targets_add(self):
        version = '1.0'
        bundle_dir = 'dummy'
        key_dirs = ['c:\\my_private_keys']
        skip_patch = True
        options = argparse.Namespace(
            app_version=version,
            bundle_dir=bundle_dir,
            key_dirs=key_dirs,
            skip_patch=skip_patch,
        )
        with patch('tufup.repo.cli.Repository', self.mock_repo_class):
            tufup.repo.cli._cmd_targets(options=options)
        self.mock_repo.add_bundle.assert_called_with(
            new_version=version, new_bundle_dir=bundle_dir, skip_patch=skip_patch,
        )
        self.mock_repo.publish_changes.assert_called_with(
            private_key_dirs=key_dirs
        )

    def test__cmd_targets_remove_latest(self):
        key_dirs = ['c:\\my_private_keys']
        options = argparse.Namespace(key_dirs=key_dirs)
        with patch('tufup.repo.cli.Repository', self.mock_repo_class):
            tufup.repo.cli._cmd_targets(options=options)
        self.mock_repo.remove_latest_bundle.assert_called()

    def test__cmd_sign_threshold(self):
        role_name = 'root'
        key_dirs = ['c:\\my_private_keys']
        options = argparse.Namespace(
            role_name=role_name, key_dirs=key_dirs, expiration_days=None
        )
        with patch('tufup.repo.cli.Repository', self.mock_repo_class):
            tufup.repo.cli._cmd_sign(options=options)
        self.mock_repo.threshold_sign.assert_called_with(
            role_name=role_name, private_key_dirs=key_dirs
        )

    def test__cmd_sign_expired(self):
        role_name = 'root'
        key_dirs = ['c:\\my_private_keys']
        options = argparse.Namespace(
            role_name=role_name,
            key_dirs=key_dirs,
            expiration_days='default',  # i.e. specify -e without a value
        )
        with patch('tufup.repo.cli.Repository', self.mock_repo_class):
            tufup.repo.cli._cmd_sign(options=options)
        self.mock_repo.refresh_expiration_date.assert_called_with(
            role_name=role_name, days=self.config['expiration_days'][role_name]
        )
        self.mock_repo.publish_changes.assert_called_with(
            private_key_dirs=key_dirs
        )

    def test__get_config_from_user_no_kwargs(self):
        default = ''
        yes = 'y'
        no = 'n'
        user_inputs = iter(
            [
                'my-app',  # app name
                'my_app.__version__',  # app version
                'repo/dir',  # repo dir
                'keys/dir',  # keys dir
                yes,  # keep default root key name
                yes,  # add another root key name
                'root-2',  # key name
                no,  # add another root key name
                default,  # encrypt 'root' key
                yes,  # encrypt 'root-2' key
                '365',  # expiration days
                '2',  # signature threshold
                default,  # keep default targets key name
                default,  # add another targets key name
                default,  # encrypt 'targets' key
                '30',  # expiration days
                '1',  # signature threshold
                default,  # keep default snapshot key name
                default,  # add another snapshot key name
                default,  # encrypt 'snapshot' key
                '7',  # expiration days
                '1',  # signature threshold
                default,  # keep default timestamp key name
                default,  # add another timestamp key name
                default,  # encrypt 'timestamp' key
                '1',  # expiration days
                '1',  # signature threshold
            ]
        )
        with patch('builtins.input', lambda *_, **__: next(user_inputs)):
            config_kwargs = tufup.repo.cli._get_config_from_user()
        self.assertTrue(config_kwargs)

    def test__get_config_from_user_with_kwargs(self):
        role_names = ['root', 'targets', 'snapshot', 'timestamp']
        original_kwargs = dict(
            app_name='my-app',
            app_version_attr='my_app.__version__',
            repo_dir='repo/dir',
            keys_dir='keys/dir',
            key_map={name: [name] for name in role_names},
            encrypted_keys=['root'],
            expiration_days={name: 1 for name in role_names},
            thresholds={name: 1 for name in role_names},
        )
        default = ''
        with patch('builtins.input', Mock(return_value=default)):
            config_kwargs = tufup.repo.cli._get_config_from_user(
                **original_kwargs
            )
        self.assertEqual(config_kwargs, original_kwargs)
