# Copyright 2014 Amazon.com, Inc. or its affiliates. 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. A copy of
# the License is located at
#
#     http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file 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.mock import patch, Mock

from botocore.session import get_session
from botocore import UNSIGNED
import os

from awscli.testutils import mock, unittest, capture_output
from awscli.customizations import globalargs


class FakeParsedArgs(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def __getattr__(self, arg):
        return None


class FakeSession(object):

    def __init__(self, session_vars=None, config_file_vars=None):
        if session_vars is None:
            session_vars = {}
        self.session_var_map = session_vars
        if config_file_vars is None:
            config_file_vars = {}
        self.config_file_vars = config_file_vars

    def get_config_variable(self, name, methods=('env', 'config'),
                            default=None):
        value = None
        config_name, envvar_name = self.session_var_map[name]
        if methods is not None:
            if 'env' in methods and value is None:
                value = os.environ.get(envvar_name)
            if 'config' in methods and value is None:
                value = self.config_file_vars.get(config_name)
        else:
            value = default
        return value


class TestGlobalArgsCustomization(unittest.TestCase):

    def test_parse_query(self):
        parsed_args = FakeParsedArgs(query='foo.bar')
        globalargs.resolve_types(parsed_args)
        # Assert that it looks like a jmespath parsed expression.
        self.assertFalse(isinstance(parsed_args.query, str))
        self.assertTrue(hasattr(parsed_args.query, 'search'))

    def test_parse_query_error_message(self):
        # Invalid jmespath expression.
        parsed_args = FakeParsedArgs(query='foo.bar.')
        with self.assertRaises(ValueError):
            globalargs.resolve_types(parsed_args)
            globalargs.resolve_types('query')

    def test_parse_verify_ssl_default_value(self):
        with mock.patch('os.environ', {}):
            parsed_args = FakeParsedArgs(verify_ssl=True, ca_bundle=None)
            session_var_map = {'ca_bundle': ('ca_bundle', 'AWS_CA_BUNDLE')}
            session = FakeSession(session_vars=session_var_map)
            globalargs.resolve_verify_ssl(parsed_args, session)
            # None, so that botocore can apply it's default logic.
            self.assertIsNone(parsed_args.verify_ssl)

    def test_parse_verify_ssl_verify_turned_off(self):
        with mock.patch('os.environ', {}):
            parsed_args = FakeParsedArgs(verify_ssl=False, ca_bundle=None)
            session_var_map = {'ca_bundle': ('ca_bundle', 'AWS_CA_BUNDLE')}
            session = FakeSession(session_vars=session_var_map)
            globalargs.resolve_verify_ssl(parsed_args, session)
            self.assertFalse(parsed_args.verify_ssl)

    def test_cli_overrides_cert_bundle(self):
        environ = {}
        with mock.patch('os.environ', environ):
            parsed_args = FakeParsedArgs(
                verify_ssl=True,
                ca_bundle='/path/to/cli_bundle.pem')
            config_file_vars = {}
            session_var_map = {'ca_bundle': ('ca_bundle', 'AWS_CA_BUNDLE')}
            session = FakeSession(
                session_vars=session_var_map,
                config_file_vars=config_file_vars)
            globalargs.resolve_verify_ssl(parsed_args, session)
            self.assertEqual(parsed_args.verify_ssl, '/path/to/cli_bundle.pem')

    def test_cli_overrides_env_cert_bundle(self):
        environ = {
            'AWS_CA_BUNDLE': '/path/to/env_bundle.pem',
        }
        with mock.patch('os.environ', environ):
            parsed_args = FakeParsedArgs(
                verify_ssl=True,
                ca_bundle='/path/to/cli_bundle.pem')
            config_file_vars = {}
            session_var_map = {'ca_bundle': ('ca_bundle', 'AWS_CA_BUNDLE')}
            session = FakeSession(
                session_vars=session_var_map,
                config_file_vars=config_file_vars)
            globalargs.resolve_verify_ssl(parsed_args, session)
            self.assertEqual(parsed_args.verify_ssl, '/path/to/cli_bundle.pem')

    def test_no_verify_ssl_overrides_cli_cert_bundle(self):
        environ = {
            'AWS_CA_BUNDLE': '/path/to/env_bundle.pem',
        }
        with mock.patch('os.environ', environ):
            parsed_args = FakeParsedArgs(
                verify_ssl=False,
                ca_bundle='/path/to/cli_bundle.pem')
            config_file_vars = {}
            session_var_map = {'ca_bundle': ('ca_bundle', 'AWS_CA_BUNDLE')}
            session = FakeSession(
                session_vars=session_var_map,
                config_file_vars=config_file_vars)
            globalargs.resolve_verify_ssl(parsed_args, session)
            self.assertFalse(parsed_args.verify_ssl)

    def test_no_sign_request_if_option_specified(self):
        args = FakeParsedArgs(sign_request=False)
        session = mock.Mock()
        with mock.patch('awscli.customizations.globalargs._update_default_client_config') as mock_update:
            globalargs.no_sign_request(args, session)
            mock_update.assert_called_once_with(session, 'signature_version', UNSIGNED)

    def test_request_signed_by_default(self):
        args = FakeParsedArgs(sign_request=True)
        session = mock.Mock()
        globalargs.no_sign_request(args, session)
        self.assertFalse(session.register.called)

    def test_invalid_endpoint_url(self):
        # Invalid jmespath expression.
        parsed_args = FakeParsedArgs(endpoint_url='missing-scheme.com')
        with self.assertRaises(ValueError):
            globalargs.resolve_types(parsed_args)

    def test_valid_endpoint_url(self):
        parsed_args = FakeParsedArgs(endpoint_url='http://custom-endpoint.com')
        globalargs.resolve_types(parsed_args)
        self.assertEqual(parsed_args.endpoint_url,
                         'http://custom-endpoint.com')

    def test_cli_read_timeout(self):
        parsed_args = FakeParsedArgs(read_timeout='60')
        session = get_session()
        globalargs.resolve_cli_read_timeout(parsed_args, session)
        self.assertEqual(parsed_args.read_timeout, 60)
        self.assertEqual(
            session.get_default_client_config().read_timeout, 60)

    def test_cli_connect_timeout(self):
        parsed_args = FakeParsedArgs(connect_timeout='60')
        session = get_session()
        globalargs.resolve_cli_connect_timeout(parsed_args, session)
        self.assertEqual(parsed_args.connect_timeout, 60)
        self.assertEqual(
            session.get_default_client_config().connect_timeout, 60)

    def test_cli_read_timeout_for_blocking(self):
        parsed_args = FakeParsedArgs(read_timeout='0')
        session = get_session()
        globalargs.resolve_cli_read_timeout(parsed_args, session)
        self.assertEqual(parsed_args.read_timeout, None)
        self.assertEqual(
            session.get_default_client_config().read_timeout, None)

    def test_cli_connect_timeout_for_blocking(self):
        parsed_args = FakeParsedArgs(connect_timeout='0')
        session = get_session()
        globalargs.resolve_cli_connect_timeout(parsed_args, session)
        self.assertEqual(parsed_args.connect_timeout, None)
        self.assertEqual(
            session.get_default_client_config().connect_timeout, None)

    def test_register_feature_id(self):
        parsed_args = FakeParsedArgs(v2_debug=True)
        session = get_session()
        globalargs.detect_migration_breakage(
            parsed_args,
            session,
            [],
        )
        # Verify the correct feature ID is registered during the
        # provide-client-params event.
        with (mock.patch('awscli.customizations.globalargs.register_feature_id')
              as mock_register_feature_id):
            session.emit(
                'provide-client-params.s3.ListBuckets',
                params={},
                model={},
            )
        mock_register_feature_id.assert_any_call(
            'CLI_V1_TO_V2_MIGRATION_DEBUG_MODE'
        )

    def test_ecr_login_v2_debug(self):
        parsed_args = FakeParsedArgs(command='ecr', v2_debug=True)
        remaining_args = ['get-login']
        session = get_session()
        with capture_output() as output:
            globalargs.detect_migration_breakage(
                parsed_args,
                session,
                remaining_args,
            )
        # Verify the expected warning is printed
        self.assertIn(
            'AWS CLI v2 UPGRADE WARNING: The `ecr get-login` command has '
            'been removed in AWS CLI v2.',
            output.stderr.getvalue()
        )

    def test_ecr_login_v2_debug_env_var(self):
        parsed_args = FakeParsedArgs(command='ecr')
        remaining_args = ['get-login']
        session = get_session()
        env = {'AWS_CLI_UPGRADE_DEBUG_MODE': 'true'}
        with capture_output() as output:
            with mock.patch('os.environ', env):
                globalargs.detect_migration_breakage(
                    parsed_args,
                    session,
                    remaining_args,
                )
        # Verify the expected warning is printed
        self.assertIn(
            'AWS CLI v2 UPGRADE WARNING: The `ecr get-login` command has '
            'been removed in AWS CLI v2.',
            output.stderr.getvalue()
        )

    def test_v2_debug_python_utf8_env_var(self):
        parsed_args = FakeParsedArgs(v2_debug=True)
        session = get_session()
        environ = {'PYTHONUTF8': '1'}
        with mock.patch('os.environ', environ):
            with capture_output() as output:
                globalargs.detect_migration_breakage(parsed_args, session, [])
                self.assertIn(
                    'The AWS CLI v2 does not support The `PYTHONUTF8` and '
                    '`PYTHONIOENCODING` environment variables, and instead '
                    'uses the `AWS_CLI_FILE_ENCODING` variable',
                    output.stderr.getvalue()
                )

    def test_v2_debug_python_utf8_resolved_env_var(self):
        parsed_args = FakeParsedArgs(v2_debug=True)
        session = get_session()
        environ = {'PYTHONUTF8': '1', 'AWS_CLI_FILE_ENCODING': 'UTF-8'}
        with mock.patch('os.environ', environ):
            with capture_output() as output:
                globalargs.detect_migration_breakage(parsed_args, session, [])
                self.assertNotIn(
                    'AWS CLI v2 UPGRADE WARNING: The PYTHONUTF8 and '
                    'PYTHONIOENCODING environment variables are unsupported '
                    'in AWS CLI v2.',
                    output.stderr.getvalue()
                )

    def test_v2_debug_python_io_encoding_env_var(self):
        parsed_args = FakeParsedArgs(v2_debug=True)
        session = get_session()
        environ = {'PYTHONIOENCODING': 'UTF8'}
        with mock.patch('os.environ', environ):
            with capture_output() as output:
                globalargs.detect_migration_breakage(parsed_args, session, [])
                self.assertIn(
                    'The AWS CLI v2 does not support The `PYTHONUTF8` and '
                    '`PYTHONIOENCODING` environment variables, and instead '
                    'uses the `AWS_CLI_FILE_ENCODING` variable',
                    output.stderr.getvalue()
                )

    def test_v2_debug_s3_sigv2(self):
        parsed_args = FakeParsedArgs(v2_debug=True)
        session = get_session()
        globalargs.detect_migration_breakage(parsed_args, session, [])
        with capture_output() as output:
            session.emit(
                'choose-signer.s3.*',
                signing_name='s3',
                region_name='us-west-2',
                signature_version='v2',
                context={'auth_type': 'v2'},
            )
        self.assertIn(
            'AWS CLI v2 UPGRADE WARNING: The AWS CLI v2 only uses Signature '
            'v4 to authenticate Amazon S3 requests.',
            output.stderr.getvalue()
        )

    def test_v2_debug_s3_us_east_1(self):
        parsed_args = FakeParsedArgs(v2_debug=True, region='us-east-1')
        session = get_session()
        globalargs.detect_migration_breakage(parsed_args, session, [])
        def mock_get(key: str):
            if key == 'retries':
                return {'invocation-id': '012345'}
            return None

        with capture_output() as output:
            mock_request = Mock()
            mock_request.url = 'https://s3.amazonaws.com'
            mock_request.context.get.side_effect = mock_get
            mock_request.headers = {}

            session.emit(
                'request-created.s3.ListBuckets',
                request=mock_request,
                operation_name='ListBuckets',
            )
        self.assertIn(
            'AWS CLI v2 UPGRADE WARNING: When you configure AWS CLI v2 '
            'to use the `us-east-1` region, it uses the true regional '
            'endpoint rather than the global endpoint.',
            output.stderr.getvalue()
        )

    def test_v2_debug_s3api_us_east_1(self):
        parsed_args = FakeParsedArgs(v2_debug=True, region='us-east-1')
        session = get_session()
        globalargs.detect_migration_breakage(parsed_args, session, [])

        def mock_get(key: str):
            if key == 'retries':
                return {'invocation-id': '012345'}
            return None

        with capture_output() as output:
            mock_request = Mock()
            mock_request.url = 'https://s3.amazonaws.com'
            mock_request.context.get.side_effect = mock_get
            mock_request.headers = {}

            session.emit(
                'request-created.s3api.ListBuckets',
                request=mock_request,
                operation_name='ListBuckets',
            )
        self.assertIn(
            'AWS CLI v2 UPGRADE WARNING: When you configure AWS CLI v2 '
            'to use the `us-east-1` region, it uses the true regional '
            'endpoint rather than the global endpoint.',
            output.stderr.getvalue()
        )