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

import regex as re

import cfnlint.helpers
from cfnlint.data import AdditionalSpecs
from cfnlint.rules import CloudFormationLintRule, RuleMatch


class BasedOnValue(CloudFormationLintRule):
    """Generic rule for checking properties based on value"""

    spec_type = ""
    message = ""

    def __init__(self):
        super().__init__()
        basedonvalue = cfnlint.helpers.load_resource(
            AdditionalSpecs, "BasedOnValue.json"
        )
        self.resource_types_specs = basedonvalue["ResourceTypes"]
        self.property_types_specs = basedonvalue["PropertyTypes"]
        for resource_type, resource_specs in self.resource_types_specs.items():
            for based_on_specs in resource_specs.values():
                for spec in based_on_specs:
                    if spec.get(self.spec_type):
                        self.resource_property_types.append(resource_type)
        for resource_type, resource_specs in self.property_types_specs.items():
            for based_on_specs in resource_specs.values():
                for spec in based_on_specs:
                    if spec.get(self.spec_type):
                        self.resource_sub_property_types.append(resource_type)

    def _check_value(self, value, spec, cfn):
        """Checks a value to see if it fits in the spec"""
        if isinstance(value, str):
            regex = spec.get("Regex")
            if regex:
                if re.match(spec.get("Regex"), value):
                    return True
        elif isinstance(value, dict):
            if len(value) == 1:
                for k, v in value.items():
                    ref = spec.get("Ref")
                    if ref:
                        if k == "Ref":
                            if isinstance(v, str):
                                return (
                                    cfn.template.get("Resources").get(v, {}).get("Type")
                                    in ref
                                )

                    getatt = spec.get("GetAtt")
                    if getatt:
                        if k == "Fn::GetAtt":
                            if isinstance(v, list):
                                restype = (
                                    cfn.template.get("Resources").get(v[0]).get("Type")
                                )
                                if restype in getatt:
                                    return getatt.get(restype) == v[1]

        return False

    # pylint: disable=unused-argument
    def _check_prop(self, prop, scenario):
        return False

    def _check_obj(self, properties, specs, path, cfn):
        matches = []
        for k, v in specs.items():
            for s in v:
                spec_values = s.get(self.spec_type)
                if spec_values:
                    property_set = [k] + spec_values
                    scenarios = cfn.get_object_without_conditions(
                        properties, property_names=property_set
                    )
                    for scenario in scenarios:
                        if self._check_value(scenario.get("Object").get(k), s, cfn):
                            for property_name in spec_values:
                                if self._check_prop(property_name, scenario):
                                    if scenario["Scenario"] is None:
                                        message = "When property '{0}' has its current value property '{1}' {2} at {3}"
                                        matches.append(
                                            RuleMatch(
                                                path,
                                                message.format(
                                                    k,
                                                    property_name,
                                                    self.message,
                                                    "/".join(map(str, path)),
                                                ),
                                            )
                                        )
                                    else:
                                        scenario_text = " and ".join(
                                            [
                                                f'when condition "{k}" is {v}'
                                                for (k, v) in scenario[
                                                    "Scenario"
                                                ].items()
                                            ]
                                        )
                                        message = "When property '{0}' has its current value property '{1}' {2} when {3}"
                                        matches.append(
                                            RuleMatch(
                                                path,
                                                message.format(
                                                    k,
                                                    property_name,
                                                    self.message,
                                                    scenario_text,
                                                ),
                                            )
                                        )

        return matches

    def match_resource_properties(self, properties, resource_type, path, cfn):
        """Check CloudFormation Properties"""
        matches = []

        if properties is None:
            # covered under rule E3001.  If there are required properties properties is required first
            return matches

        # Need to get this spec
        specs = self.resource_types_specs.get(resource_type)

        matches.extend(self._check_obj(properties, specs, path, cfn))

        return matches

    def match_resource_sub_properties(self, properties, property_type, path, cfn):
        """Check CloudFormation Properties"""
        matches = []

        if properties is None:
            # covered under rule E3001.  If there are required properties properties is required first
            return matches

        # Need to get this spec
        specs = self.property_types_specs.get(property_type)

        matches.extend(self._check_obj(properties, specs, path, cfn))

        return matches
