"""Tests for certbot._internal.plugins.manual"""
import sys
import textwrap
import unittest
from unittest import mock

import pytest

from acme import challenges
from certbot import errors
from certbot.compat import filesystem
from certbot.compat import os
from certbot.tests import acme_util
from certbot.tests import util as test_util


class AuthenticatorTest(test_util.TempDirTestCase):
    """Tests for certbot._internal.plugins.manual.Authenticator."""

    def setUp(self):
        super().setUp()
        get_display_patch = test_util.patch_display_util()
        self.mock_get_display = get_display_patch.start()
        self.addCleanup(get_display_patch.stop)

        self.http_achall = acme_util.HTTP01_A
        self.dns_achall = acme_util.DNS01_A
        self.dns_achall_2 = acme_util.DNS01_A_2
        self.achalls = [self.http_achall, self.dns_achall, self.dns_achall_2]
        for d in ["config_dir", "work_dir", "in_progress"]:
            filesystem.mkdir(os.path.join(self.tempdir, d))
            # "backup_dir" and "temp_checkpoint_dir" get created in
            # certbot.util.make_or_verify_dir() during the Reverter
            # initialization.
        self.config = mock.MagicMock(
            http01_port=0, manual_auth_hook=None, manual_cleanup_hook=None,
            noninteractive_mode=False, validate_hooks=False,
            config_dir=os.path.join(self.tempdir, "config_dir"),
            work_dir=os.path.join(self.tempdir, "work_dir"),
            backup_dir=os.path.join(self.tempdir, "backup_dir"),
            temp_checkpoint_dir=os.path.join(
                                        self.tempdir, "temp_checkpoint_dir"),
            in_progress_dir=os.path.join(self.tempdir, "in_progess"))

        from certbot._internal.plugins.manual import Authenticator
        self.auth = Authenticator(self.config, name='manual')

    def test_prepare_no_hook_noninteractive(self):
        self.config.noninteractive_mode = True
        with pytest.raises(errors.PluginError):
            self.auth.prepare()

    def test_prepare_bad_hook(self):
        self.config.manual_auth_hook = os.path.abspath(os.sep)  # is / on UNIX
        self.config.validate_hooks = True
        with pytest.raises(errors.HookCommandNotFound):
            self.auth.prepare()

    def test_more_info(self):
        assert isinstance(self.auth.more_info(), str)

    def test_get_chall_pref(self):
        assert self.auth.get_chall_pref('example.org') == \
                         [challenges.HTTP01, challenges.DNS01]

    def test_script_perform(self):
        self.config.manual_auth_hook = (
            '{0} -c "'
            'from certbot.compat import os;'
            'print(os.environ.get(\'CERTBOT_DOMAIN\'));'
            'print(os.environ.get(\'CERTBOT_TOKEN\', \'notoken\'));'
            'print(os.environ.get(\'CERTBOT_VALIDATION\', \'novalidation\'));'
            'print(os.environ.get(\'CERTBOT_ALL_DOMAINS\'));'
            'print(os.environ.get(\'CERTBOT_REMAINING_CHALLENGES\'));"'
            .format(sys.executable))
        dns_expected = '{0}\n{1}\n{2}\n{3}\n{4}'.format(
            self.dns_achall.domain, 'notoken',
            self.dns_achall.validation(self.dns_achall.account_key),
            ','.join(achall.domain for achall in self.achalls),
            len(self.achalls) - self.achalls.index(self.dns_achall) - 1)
        http_expected = '{0}\n{1}\n{2}\n{3}\n{4}'.format(
            self.http_achall.domain, self.http_achall.chall.encode('token'),
            self.http_achall.validation(self.http_achall.account_key),
            ','.join(achall.domain for achall in self.achalls),
            len(self.achalls) - self.achalls.index(self.http_achall) - 1)

        assert self.auth.perform(self.achalls) == \
            [achall.response(achall.account_key) for achall in self.achalls]
        assert self.auth.env[self.dns_achall]['CERTBOT_AUTH_OUTPUT'] == \
            dns_expected
        assert self.auth.env[self.http_achall]['CERTBOT_AUTH_OUTPUT'] == \
            http_expected

        # Successful hook output should be sent to notify
        assert self.mock_get_display().notification.call_count == len(self.achalls)
        for i, (args, _) in enumerate(self.mock_get_display().notification.call_args_list):
            needle = textwrap.indent(self.auth.env[self.achalls[i]]['CERTBOT_AUTH_OUTPUT'], ' ')
            assert needle in args[0]

    def test_manual_perform(self):
        assert self.auth.perform(self.achalls) == \
            [achall.response(achall.account_key) for achall in self.achalls]

        assert self.mock_get_display().notification.call_count == len(self.achalls)
        for i, (args, kwargs) in enumerate(self.mock_get_display().notification.call_args_list):
            achall = self.achalls[i]
            assert achall.validation(achall.account_key) in args[0]
            assert kwargs['wrap'] is False

    def test_cleanup(self):
        self.config.manual_auth_hook = ('{0} -c "import sys; sys.stdout.write(\'foo\')"'
                                        .format(sys.executable))
        self.config.manual_cleanup_hook = '# cleanup'
        self.auth.perform(self.achalls)

        for achall in self.achalls:
            self.auth.cleanup([achall])
            assert os.environ['CERTBOT_AUTH_OUTPUT'] == 'foo'
            assert os.environ['CERTBOT_DOMAIN'] == achall.domain
            if isinstance(achall.chall, (challenges.HTTP01, challenges.DNS01)):
                assert os.environ['CERTBOT_VALIDATION'] == \
                    achall.validation(achall.account_key)
            if isinstance(achall.chall, challenges.HTTP01):
                assert os.environ['CERTBOT_TOKEN'] == \
                    achall.chall.encode('token')
            else:
                assert 'CERTBOT_TOKEN' not in os.environ

    def test_auth_hint_hook(self):
        self.config.manual_auth_hook = '/bin/true'
        assert self.auth.auth_hint([acme_util.DNS01_A, acme_util.HTTP01_A]) == \
            'The Certificate Authority failed to verify the DNS TXT records and challenge ' \
            'files created by the --manual-auth-hook. Ensure that this hook is functioning ' \
            'correctly and that it waits a sufficient duration of time for DNS propagation. ' \
            'Refer to "certbot --help manual" and the Certbot User Guide.'
        assert self.auth.auth_hint([acme_util.HTTP01_A]) == \
            'The Certificate Authority failed to verify the challenge files created by the ' \
            '--manual-auth-hook. Ensure that this hook is functioning correctly. Refer to ' \
            '"certbot --help manual" and the Certbot User Guide.'

    def test_auth_hint_no_hook(self):
        assert self.auth.auth_hint([acme_util.DNS01_A, acme_util.HTTP01_A]) == \
            'The Certificate Authority failed to verify the manually created DNS TXT records ' \
            'and challenge files. Ensure that you created these in the correct location, or ' \
            'try waiting longer for DNS propagation on the next attempt.'
        assert self.auth.auth_hint([acme_util.HTTP01_A, acme_util.HTTP01_A, acme_util.HTTP01_A]) == \
            'The Certificate Authority failed to verify the manually created challenge files. ' \
            'Ensure that you created these in the correct location.'


if __name__ == '__main__':
    sys.exit(pytest.main(sys.argv[1:] + [__file__]))  # pragma: no cover
