"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""

from cfnlint.rules import CloudFormationLintRule, RuleMatch


class Select(CloudFormationLintRule):
    """Check if Select values are correct"""

    id = "E1017"
    shortdesc = "Select validation of parameters"
    description = "Making sure the Select function is properly configured"
    source_url = "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-select.html"
    tags = ["functions", "select"]
    supported_functions = [
        "Fn::FindInMap",
        "Fn::GetAtt",
        "Fn::GetAZs",
        "Fn::If",
        "Fn::Split",
        "Fn::Cidr",
        "Fn::Select",  # issue: 2895
        "Ref",
    ]

    def _test_index_obj(self, index_obj, path):
        matches = []
        if isinstance(index_obj, dict):
            if len(index_obj) == 1:
                for index_key, _ in index_obj.items():
                    if index_key not in [
                        "Ref",
                        "Fn::FindInMap",
                        "Fn::Select",
                    ]:
                        message = "Select index should be an Integer or a function Ref, Fn::FindInMap, or Fn::Select for {0}"
                        matches.append(
                            RuleMatch(
                                path,
                                message.format("/".join(map(str, path))),
                            )
                        )
            else:
                message = "Select index should be an Integer or a function Ref, Fn::FindInMap, or Fn::Select for {0}"
                matches.append(
                    RuleMatch(
                        path,
                        message.format("/".join(map(str, path))),
                    )
                )
        elif not isinstance(index_obj, int):
            try:
                int(index_obj)
            except (ValueError, TypeError):
                message = "Select index should be an Integer or a function of Ref, Fn::FindInMap, or Fn::Select for {0}"
                matches.append(
                    RuleMatch(path, message.format("/".join(map(str, path))))
                )

        return matches

    def _test_list_obj(self, list_obj, path):
        matches = []
        if isinstance(list_obj, dict):
            if len(list_obj) == 1:
                for key, _ in list_obj.items():
                    if key not in self.supported_functions:
                        message = "Select should use a supported function of {0}"
                        matches.append(
                            RuleMatch(
                                path,
                                message.format(
                                    ", ".join(map(str, self.supported_functions))
                                ),
                            )
                        )
            else:
                message = "Select should use a supported function of {0}"
                matches.append(
                    RuleMatch(
                        path,
                        message.format(", ".join(map(str, self.supported_functions))),
                    )
                )
        elif not isinstance(list_obj, list):
            message = "Select should be an array of values for {0}"
            matches.append(RuleMatch(path, message.format("/".join(map(str, path)))))

        return matches

    def _test_select_obj(self, select_obj, path):
        matches = []
        if not isinstance(select_obj, list):
            message = "Select should be a list of 2 elements for {0}"
            matches.append(RuleMatch(path, message.format("/".join(map(str, path)))))
            return matches
        if len(select_obj) != 2:
            message = "Select should be a list of 2 elements for {0}"
            matches.append(RuleMatch(path, message.format("/".join(map(str, path)))))
            return matches

        index_obj = select_obj[0]
        list_of_objs = select_obj[1]
        matches.extend(self._test_index_obj(index_obj, path[:] + [0]))
        matches.extend(self._test_list_obj(list_of_objs, path[:] + [1]))

        return matches

    def match(self, cfn):
        matches = []

        select_objs = cfn.search_deep_keys("Fn::Select")

        for select_obj in select_objs:
            select_value_obj = select_obj[-1]
            tree = select_obj[:-1]
            matches.extend(self._test_select_obj(select_value_obj, tree[:]))

        return matches
