# -*- coding: utf-8 -*-

# Copyright (C) 2012-2018 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.  You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#

from __future__ import absolute_import
from __future__ import unicode_literals

import argparse
import os
import re
from argparse import Namespace

import dnf.cli.cli
import dnf.conf
import dnf.goal
import dnf.repo
import dnf.repodict

import tests.support
from tests.support import mock


VERSIONS_OUTPUT = """\
  Installed: pepper-0:20-0.x86_64 at Thu Jan  1 00:00:00 1970
  Built    :  at Thu Jan  1 00:00:00 1970

  Installed: tour-0:5-0.noarch at Thu Jan  1 00:00:00 1970
  Built    :  at Thu Jan  1 00:00:00 1970
"""


class VersionStringTest(tests.support.DnfBaseTestCase):

    REPOS = []

    def test_print_versions(self):
        output = tests.support.MockOutput()
        with mock.patch('sys.stdout') as stdout,\
                mock.patch('dnf.sack._rpmdb_sack', return_value=self.base.sack):
            dnf.cli.cli.print_versions(['pepper', 'tour'], self.base, output)
        written = ''.join([mc[1][0] for mc in stdout.method_calls
                           if mc[0] == 'write'])
        self.assertEqual(written, VERSIONS_OUTPUT)


@mock.patch('dnf.cli.cli.logger', new_callable=tests.support.mock_logger)
class BaseCliTest(tests.support.ResultTestCase):

    REPOS = ["main", "updates"]
    BASE_CLI = True
    INIT_SACK = True

    def setUp(self):
        super(BaseCliTest, self).setUp()
        self.base.output.term = tests.support.MockTerminal()
        self.base.downgrade_to = mock.Mock(wraps=self.base.downgrade_to)

    def test_downgradePkgs(self, logger):
        self.base.downgradePkgs(('tour',))

        self.assertEqual(self.base.downgrade_to.mock_calls, [mock.call('tour', strict=False)])
        self.assertEqual(logger.mock_calls, [])

    def test_downgradePkgs_notfound(self, logger):
        with self.assertRaises(dnf.exceptions.Error) as ctx:
            self.base.downgradePkgs(('non-existent',))
        self.assertEqual(str(ctx.exception), 'No packages marked for downgrade.')

        self.assertEqual(self.base.downgrade_to.mock_calls,
                         [mock.call('non-existent', strict=False)])
        self.assertEqual(logger.mock_calls,
                         [mock.call.info('No package %s available.',
                                         'non-existent')])

    @mock.patch('dnf.cli.cli._', dnf.pycomp.NullTranslations().ugettext)
    def test_downgradePkgs_notinstalled(self, logger):
        tests.support.ObjectMatcher(dnf.package.Package, {'name': 'lotus'})

        with self.assertRaises(dnf.exceptions.Error) as ctx:
            self.base.downgradePkgs(('lotus',))
        self.assertEqual(str(ctx.exception), 'No packages marked for downgrade.')

        self.assertEqual(self.base.downgrade_to.mock_calls, [mock.call('lotus', strict=False)])


@mock.patch('dnf.cli.cli.Cli._read_conf_file')
class CliTest(tests.support.DnfBaseTestCase):

    REPOS = ["main"]
    CLI = "init"

    def setUp(self):
        super(CliTest, self).setUp()
        self.base.output = tests.support.MockOutput()

    def test_knows_upgrade(self, _):
        upgrade = self.cli.cli_commands['upgrade']
        update = self.cli.cli_commands['update']
        self.assertIs(upgrade, update)

    def test_simple(self, _):
        self.assertFalse(self.base.conf.assumeyes)
        self.cli.configure(['update', '-y'])
        self.assertTrue(self.base.conf.assumeyes)

    def test_glob_options_cmds(self, _):
        params = [
            ['install', '-y', 'pkg1', 'pkg2'],
            ['install', 'pkg1', '-y', 'pkg2'],
            ['install', 'pkg1', 'pkg2', '-y'],
            ['-y', 'install', 'pkg1', 'pkg2']
        ]
        for param in params:
            self.cli.configure(args=param)
            self.assertTrue(self.base.conf.assumeyes)
            self.assertEqual(self.cli.command.opts.command, "install")
            self.assertEqual(self.cli.command.opts.pkg_specs, ["pkg1", "pkg2"])

    def test_configure_repos(self, _):
        opts = Namespace()
        opts.repo = []
        opts.repos_ed = [('*', 'disable'), ('comb', 'enable')]
        opts.cacheonly = True
        opts.nogpgcheck = True
        opts.repofrompath = {}
        self.base._repos = dnf.repodict.RepoDict()
        self.base._repos.add(tests.support.MockRepo('one', self.base.conf))
        self.base._repos.add(tests.support.MockRepo('two', self.base.conf))
        self.base._repos.add(tests.support.MockRepo('comb', self.base.conf))
        self.cli._configure_repos(opts)
        self.assertFalse(self.base.repos['one'].enabled)
        self.assertFalse(self.base.repos['two'].enabled)
        self.assertTrue(self.base.repos['comb'].enabled)
        self.assertFalse(self.base.repos["comb"].gpgcheck)
        self.assertFalse(self.base.repos["comb"].repo_gpgcheck)

    def test_configure_repos_expired(self, _):
        """Ensure that --cacheonly beats the expired status."""
        opts = Namespace()
        opts.repo = []
        opts.repos_ed = []
        opts.cacheonly = True
        opts.repofrompath = {}

        pers = self.base._repo_persistor
        pers.get_expired_repos = mock.Mock(return_value=('one',))
        self.base._repos = dnf.repodict.RepoDict()
        self.base._repos.add(tests.support.MockRepo('one', self.base.conf))
        self.cli._configure_repos(opts)
        # _process_demands() should respect --cacheonly in spite of modified demands
        self.cli.demands.fresh_metadata = False
        self.cli.demands.cacheonly = True
        self.cli._process_demands()
        self.assertEqual(self.base.repos['one']._repo.getSyncStrategy(),
                         dnf.repo.SYNC_ONLY_CACHE)


@mock.patch('dnf.logging.Logging._setup', new=mock.MagicMock)
class ConfigureTest(tests.support.DnfBaseTestCase):

    REPOS = ["main"]
#    CLI = "init"

    def setUp(self):
        super(ConfigureTest, self).setUp()
        self.base._conf = dnf.conf.Conf()
        self.base.output = tests.support.MockOutput()
        self.base._plugins = mock.Mock()
        self.cli = dnf.cli.cli.Cli(self.base)
        self.cli.command = mock.Mock()
        self.conffile = os.path.join(tests.support.dnf_toplevel(), "etc/dnf/dnf.conf")

    @mock.patch('dnf.util.am_i_root', lambda: False)
    def test_configure_user(self):
        """ Test Cli.configure as user."""
        # call setUp() once again *after* am_i_root() is mocked so the cachedir is set as expected
        self.setUp()
        self.base._conf.installroot = self._installroot
        with mock.patch('dnf.rpm.detect_releasever', return_value=69):
            self.cli.configure(['update', '-c', self.conffile])
        reg = re.compile('^' + self._installroot + '/var/tmp/dnf-[.a-zA-Z0-9_-]+$')
        self.assertIsNotNone(reg.match(self.base.conf.cachedir))
        parser = argparse.ArgumentParser()
        expected = "%s update -c %s " % (parser.prog, self.conffile)
        self.assertEqual(self.cli.cmdstring, expected)

    @mock.patch('dnf.util.am_i_root', lambda: True)
    def test_configure_root(self):
        """ Test Cli.configure as root."""
        self.base._conf = dnf.conf.Conf()
        with mock.patch('dnf.rpm.detect_releasever', return_value=69):
            self.cli.configure(['update', '--nogpgcheck', '-c', self.conffile])
        reg = re.compile('^/var/cache/dnf$')
        self.assertIsNotNone(reg.match(self.base.conf.system_cachedir))
        parser = argparse.ArgumentParser()
        expected = "%s update --nogpgcheck -c %s " % (parser.prog, self.conffile)
        self.assertEqual(self.cli.cmdstring, expected)

    def test_configure_verbose(self):
        self.base._conf.installroot = self._installroot
        with mock.patch('dnf.rpm.detect_releasever', return_value=69):
            self.cli.configure(['-v', 'update', '-c', self.conffile])
        parser = argparse.ArgumentParser()
        expected = "%s -v update -c %s " % (parser.prog, self.conffile)
        self.assertEqual(self.cli.cmdstring, expected)
        self.assertEqual(self.base.conf.debuglevel, 6)
        self.assertEqual(self.base.conf.errorlevel, 6)

    @mock.patch('dnf.cli.cli.Cli._parse_commands', new=mock.MagicMock)
    @mock.patch('os.path.exists', return_value=True)
    def test_conf_exists_in_installroot(self, ospathexists):
        with mock.patch('logging.Logger.warning'), \
                mock.patch('dnf.rpm.detect_releasever', return_value=69):
            self.cli.configure(['--installroot', '/roots/dnf', 'update'])
        self.assertEqual(self.base.conf.config_file_path, '/roots/dnf/etc/dnf/dnf.conf')
        self.assertEqual(self.base.conf.installroot, '/roots/dnf')

    @mock.patch('dnf.cli.cli.Cli._parse_commands', new=mock.MagicMock)
    @mock.patch('os.path.exists', return_value=False)
    def test_conf_notexists_in_installroot(self, ospathexists):
        with mock.patch('dnf.rpm.detect_releasever', return_value=69):
            self.cli.configure(['--installroot', '/roots/dnf', 'update'])
        self.assertEqual(self.base.conf.config_file_path, '/etc/dnf/dnf.conf')
        self.assertEqual(self.base.conf.installroot, '/roots/dnf')

    @mock.patch('dnf.cli.cli.Cli._parse_commands', new=mock.MagicMock)
    def test_installroot_with_etc(self):
        """Test that conffile is detected in a new installroot."""
        self.base.extcmds = []

        tlv = tests.support.dnf_toplevel()
        self.cli.configure(['--installroot', tlv, 'update'])
        self.assertEqual(self.base.conf.config_file_path, '%s/etc/dnf/dnf.conf' % tlv)

    def test_installroot_configurable(self):
        """Test that conffile is detected in a new installroot."""

        conf = os.path.join(tests.support.dnf_toplevel(), "tests/etc/installroot.conf")
        self.cli.configure(['-c', conf, '--nogpgcheck', '--releasever', '17', 'update'])
        self.assertEqual(self.base.conf.installroot, '/roots/dnf')
