#!/usr/bin/env python
# Copyright 2012-2013 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.
import base64
import contextlib
import io
import platform
import sys

from botocore.compat import json

from awscli.compat import StringIO, get_stdout_text_writer
from awscli.formatter import JSONFormatter
from awscli.testutils import (
    BaseAWSCommandParamsTest,
    mock,
    skip_if_windows,
    unittest,
)


class TestGetPasswordData(BaseAWSCommandParamsTest):
    COMMAND = 'iam add-user-to-group --group-name foo --user-name bar'

    def setUp(self):
        super(TestGetPasswordData, self).setUp()
        self.parsed_response = {}

    def test_empty_dict_response_prints_nothing(self):
        # This is the default response, but we want to be explicit
        # that we're returning an empty dict.
        self.parsed_response = {}
        stdout = self.run_cmd(self.COMMAND, expected_rc=0)[0]
        self.assertEqual(stdout, '')

    def test_empty_list_prints_list(self):
        self.parsed_response = []
        stdout = self.run_cmd(self.COMMAND, expected_rc=0)[0]
        self.assertEqual(stdout, '[]\n')

    def test_empty_string_prints_nothing(self):
        self.parsed_response = ''
        stdout = self.run_cmd(self.COMMAND, expected_rc=0)[0]
        self.assertEqual(stdout, '""\n')


class TestListUsers(BaseAWSCommandParamsTest):
    def setUp(self):
        super(TestListUsers, self).setUp()
        self.parsed_response = {
            'Users': [
                {
                    "UserName": "testuser-50",
                    "Path": "/",
                    "CreateDate": "2013-02-12T19:08:52Z",
                    "UserId": "EXAMPLEUSERID",
                    "Arn": "arn:aws:iam::12345:user/testuser1",
                },
                {
                    "UserName": "testuser-51",
                    "Path": "/",
                    "CreateDate": "2012-10-14T23:53:39Z",
                    "UserId": "EXAMPLEUSERID",
                    "Arn": "arn:aws:iam::123456:user/testuser2",
                },
            ]
        }

    def test_json_response(self):
        output = self.run_cmd('iam list-users', expected_rc=0)[0]
        parsed_output = json.loads(output)
        self.assertIn('Users', parsed_output)
        self.assertEqual(len(parsed_output['Users']), 2)
        self.assertEqual(
            sorted(parsed_output['Users'][0].keys()),
            ['Arn', 'CreateDate', 'Path', 'UserId', 'UserName'],
        )

    def test_jmespath_json_response(self):
        jmespath_query = 'Users[*].UserName'
        output = self.run_cmd(
            'iam list-users --query %s' % jmespath_query, expected_rc=0
        )[0]
        parsed_output = json.loads(output)
        self.assertEqual(parsed_output, ['testuser-50', 'testuser-51'])

    def test_zero_value_is_printed(self):
        # Even though the integer 0 is false-like, we still
        # should be printing it to stdout if a jmespath query
        # evalutes to 0.
        jmespath_query = '`0`'
        output = self.run_cmd(
            'iam list-users --query %s' % jmespath_query, expected_rc=0
        )[0]
        self.assertEqual(output, '0\n')

    def test_unknown_output_type_from_env_var(self):
        # argparse already handles the case with a bad --output
        # specified on the CLI, we need to verify that a bad
        # output format from the env var still gives an error.
        self.environ['AWS_DEFAULT_OUTPUT'] = 'bad-output-type'
        self.run_cmd('iam list-users', expected_rc=255)

    @skip_if_windows('Encoding tests only supported on mac/linux')
    def test_json_prints_unicode_chars(self):
        self.parsed_response['Users'][1]['UserId'] = '\u2713'
        output = self.run_cmd('iam list-users', expected_rc=0)[0]
        with mock.patch('sys.stdout', StringIO()) as f:
            out = get_stdout_text_writer()
            out.write('\u2713')
            expected = f.getvalue()
        # We should not see the '\u<hex>' for of the unicode character.
        # It should be encoded into the default encoding.
        self.assertNotIn('\\u2713', output)
        self.assertIn(expected, output)


class TestFormattersHandleClosedPipes(unittest.TestCase):
    def test_fully_buffered_handles_io_error(self):
        args = mock.Mock(query=None)
        operation = mock.Mock(can_paginate=False)
        response = '{"Foo": "Bar"}'
        fake_closed_stream = mock.Mock(spec=StringIO)
        fake_closed_stream.flush.side_effect = IOError
        formatter = JSONFormatter(args)
        formatter('command_name', response, stream=fake_closed_stream)
        # We should not have let the IOError propogate, but
        # we still should have called the flush() on the
        # stream.
        fake_closed_stream.flush.assert_called_with()


class TestBinaryData(unittest.TestCase):
    def test_binary_data_gets_base64_encoded(self):
        args = mock.Mock(query=None)
        raw_bytes = b'foo'
        response = {'BinaryValue': raw_bytes}
        stdout_b = io.BytesIO()
        stdout = io.TextIOWrapper(stdout_b, newline='\n')
        formatter = JSONFormatter(args)

        with contextlib.redirect_stdout(stdout):
            formatter('command-name', response, sys.stdout)
            stdout.flush()

        assert (
            stdout_b.getvalue()
            == (
                '{\n'
                f'    "BinaryValue": "{base64.b64encode(raw_bytes).decode("utf-8")}"\n'
                '}\n'
            ).encode()
        )
