# 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.
import pprint

from botocore.compat import OrderedDict

from awscli.schema import (
    ParameterRequiredError,
    SchemaTransformer,
    ShapeNameGenerator,
)
from awscli.testutils import unittest

MISSING_TYPE = {
    "type": "object",
    "properties": {"Foo": {"description": "I am a foo"}},
}


class TestSchemaTransformer(unittest.TestCase):
    maxDiff = None

    def test_missing_top_level_type_raises_exception(self):
        transformer = SchemaTransformer()
        with self.assertRaises(ParameterRequiredError):
            transformer.transform({})

    def test_missing_type_raises_exception(self):
        transformer = SchemaTransformer()

        with self.assertRaises(ParameterRequiredError):
            transformer.transform(
                {
                    'type': 'object',
                    'properties': {
                        'Foo': {
                            'description': 'foo',
                        }
                    },
                }
            )

    def assert_schema_transforms_to(self, schema, transforms_to):
        transformer = SchemaTransformer()
        actual = transformer.transform(schema)
        if actual != transforms_to:
            self.fail(
                "Transform failed.\n\nExpected:\n%s\n\nActual:\n%s\n"
                % (pprint.pformat(transforms_to), pprint.pformat(actual))
            )

    def test_transforms_list_of_single_string(self):
        schema = {'type': 'array', 'items': {'type': 'string'}}
        transforms_to = {
            'InputShape': {'type': 'list', 'member': {'shape': 'StringType1'}},
            'StringType1': {'type': 'string'},
        }
        self.assert_schema_transforms_to(schema, transforms_to)

    def test_transform_list_of_structures(self):
        schema = {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "arg1": {
                        "type": "string",
                    },
                    "arg2": {
                        "type": "integer",
                    },
                },
            },
        }
        transforms_to = {
            'InputShape': {
                'type': 'list',
                'member': {'shape': 'StructureType1'},
            },
            'StructureType1': {
                'type': 'structure',
                'members': {
                    'arg1': {
                        'shape': 'StringType1',
                    },
                    'arg2': {
                        'shape': 'IntegerType1',
                    },
                },
            },
            'StringType1': {'type': 'string'},
            'IntegerType1': {'type': 'integer'},
        }
        self.assert_schema_transforms_to(schema, transforms_to)

    def test_transform_required_members_on_structure(self):
        pass

    def test_transforms_string(self):
        self.assert_schema_transforms_to(
            schema={'type': 'string'},
            transforms_to={'InputShape': {'type': 'string'}},
        )

    def test_transforms_boolean(self):
        self.assert_schema_transforms_to(
            schema={'type': 'boolean'},
            transforms_to={'InputShape': {'type': 'boolean'}},
        )

    def test_transforms_integer(self):
        self.assert_schema_transforms_to(
            schema={'type': 'integer'},
            transforms_to={'InputShape': {'type': 'integer'}},
        )

    def test_transforms_structure(self):
        self.assert_schema_transforms_to(
            schema={
                "type": "object",
                "properties": OrderedDict(
                    [
                        ("A", {"type": "string"}),
                        ("B", {"type": "string"}),
                    ]
                ),
            },
            transforms_to={
                'InputShape': {
                    'type': 'structure',
                    'members': {
                        'A': {'shape': 'StringType1'},
                        'B': {'shape': 'StringType2'},
                    },
                },
                'StringType1': {'type': 'string'},
                'StringType2': {'type': 'string'},
            },
        )

    def test_transforms_map(self):
        self.assert_schema_transforms_to(
            schema={
                "type": "map",
                "key": {"type": "string"},
                "value": {"type": "string"},
            },
            transforms_to={
                'InputShape': {
                    "type": "map",
                    "key": {"shape": "StringType1"},
                    "value": {"shape": "StringType2"},
                },
                'StringType1': {'type': 'string'},
                'StringType2': {'type': 'string'},
            },
        )

    def test_description_on_shape_type(self):
        self.assert_schema_transforms_to(
            schema={'type': 'string', 'description': 'a description'},
            transforms_to={
                'InputShape': {
                    'type': 'string',
                    'documentation': 'a description',
                }
            },
        )

    def test_enum_on_shape_type(self):
        self.assert_schema_transforms_to(
            schema={
                'type': 'string',
                'enum': ['a', 'b'],
            },
            transforms_to={
                'InputShape': {'type': 'string', 'enum': ['a', 'b']}
            },
        )

    def test_description_on_shape_ref(self):
        self.assert_schema_transforms_to(
            schema={
                'type': 'object',
                'description': 'object description',
                'properties': {
                    'A': {
                        'type': 'string',
                        'description': 'string description',
                    },
                },
            },
            transforms_to={
                'InputShape': {
                    'type': 'structure',
                    'documentation': 'object description',
                    'members': {
                        'A': {'shape': 'StringType1'},
                    },
                },
                'StringType1': {
                    'documentation': 'string description',
                    'type': 'string',
                },
            },
        )

    def test_required_members_on_structure(self):
        # This case is interesting because we actually
        # don't support a 'required' key on a member shape ref.
        # Now, all the required members are added as a key on the
        # parent structure shape.
        self.assert_schema_transforms_to(
            schema={
                'type': 'object',
                'properties': {
                    'A': {'type': 'string', 'required': True},
                },
            },
            transforms_to={
                'InputShape': {
                    'type': 'structure',
                    # This 'required' key is the change here.
                    'required': ['A'],
                    'members': {
                        'A': {'shape': 'StringType1'},
                    },
                },
                'StringType1': {'type': 'string'},
            },
        )

    def test_nested_structure(self):
        self.assert_schema_transforms_to(
            schema={
                'type': 'object',
                'properties': {
                    'A': {
                        'type': 'object',
                        'properties': {
                            'B': {
                                'type': 'object',
                                'properties': {'C': {'type': 'string'}},
                            }
                        },
                    },
                },
            },
            transforms_to={
                'InputShape': {
                    'type': 'structure',
                    'members': {
                        'A': {'shape': 'StructureType1'},
                    },
                },
                'StructureType1': {
                    'type': 'structure',
                    'members': {'B': {'shape': 'StructureType2'}},
                },
                'StructureType2': {
                    'type': 'structure',
                    'members': {'C': {'shape': 'StringType1'}},
                },
                'StringType1': {
                    'type': 'string',
                },
            },
        )


class TestShapeNameGenerator(unittest.TestCase):
    def test_generate_name_types(self):
        namer = ShapeNameGenerator()
        self.assertEqual(namer.new_shape_name('string'), 'StringType1')
        self.assertEqual(namer.new_shape_name('list'), 'ListType1')
        self.assertEqual(namer.new_shape_name('structure'), 'StructureType1')

    def test_generate_type_multiple_times(self):
        namer = ShapeNameGenerator()
        self.assertEqual(namer.new_shape_name('string'), 'StringType1')
        self.assertEqual(namer.new_shape_name('string'), 'StringType2')
