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

from cfnlint.helpers import RESOURCE_SPECS
from cfnlint.rules import CloudFormationLintRule, RuleMatch


class AllowedPattern(CloudFormationLintRule):
    """Check if properties have a valid value"""

    id = "E3031"
    shortdesc = "Check if property values adhere to a specific pattern"
    description = "Check if properties have a valid value in case of a pattern (Regular Expression)"
    source_url = "https://github.com/awslabs/cfn-python-lint/blob/main/docs/cfn-resource-specification.md#allowedpattern"
    tags = ["resources", "property", "allowed pattern", "regex"]

    def __init__(self):
        """Init"""
        super().__init__()
        self.config_definition = {
            "exceptions": {
                "default": [],
                "type": "list",
                "itemtype": "string",
            }
        }
        self.configure()

    def initialize(self, cfn):
        """Initialize the rule"""
        for resource_type_spec in RESOURCE_SPECS.get(cfn.regions[0]).get(
            "ResourceTypes"
        ):
            self.resource_property_types.append(resource_type_spec)
        for property_type_spec in RESOURCE_SPECS.get(cfn.regions[0]).get(
            "PropertyTypes"
        ):
            self.resource_sub_property_types.append(property_type_spec)

    def check_value(self, value, path, property_name, **kwargs):
        """Check Value"""
        matches = []

        # Get the Allowed Pattern Regex
        value_pattern_regex = kwargs.get("value_specs", {}).get(
            "AllowedPatternRegex", {}
        )
        # Get the "Human Readable" version for the error message. Optional, if not specified,
        # the RegEx itself is used.
        value_pattern = kwargs.get("value_specs", {}).get(
            "AllowedPattern", value_pattern_regex
        )

        if isinstance(value, (int, float)):
            value = str(value)

        if isinstance(value, str):
            if value_pattern_regex:
                regex = re.compile(value_pattern_regex, re.ASCII)

                # Ignore values with dynamic references. Simple check to prevent false-positives
                # See: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html
                if "{{resolve:" not in value:
                    if not regex.match(value):
                        for exception in self.config.get("exceptions"):
                            exception_regex = re.compile(exception)
                            if exception_regex.match(value):
                                return matches
                        full_path = "/".join(str(x) for x in path)

                        message = "{} contains invalid characters (Pattern: {}) at {}"
                        matches.append(
                            RuleMatch(
                                path,
                                message.format(property_name, value_pattern, full_path),
                            )
                        )

        return matches

    def check(self, cfn, properties, value_specs, property_specs, path):
        """Check itself"""
        matches = []
        for p_value, p_path in properties.items_safe(path[:]):
            for prop in p_value:
                if prop in value_specs:
                    value = value_specs.get(prop).get("Value", {})
                    if value:
                        value_type = value.get("ValueType", "")
                        property_type = (
                            property_specs.get("Properties").get(prop).get("Type")
                        )
                        value_specs = (
                            RESOURCE_SPECS.get(cfn.regions[0])
                            .get("ValueTypes")
                            .get(value_type, {})
                        )
                        if value_specs == "CACHED":
                            value_specs = (
                                RESOURCE_SPECS.get("us-east-1")
                                .get("ValueTypes")
                                .get(value_type, {})
                            )
                        matches.extend(
                            cfn.check_value(
                                p_value,
                                prop,
                                p_path,
                                check_value=self.check_value,
                                value_specs=value_specs,
                                cfn=cfn,
                                property_type=property_type,
                                property_name=prop,
                            )
                        )
        return matches

    def match_resource_sub_properties(self, properties, property_type, path, cfn):
        """Match for sub properties"""
        matches = []

        specs = (
            RESOURCE_SPECS.get(cfn.regions[0])
            .get("PropertyTypes")
            .get(property_type, {})
            .get("Properties", {})
        )
        property_specs = (
            RESOURCE_SPECS.get(cfn.regions[0]).get("PropertyTypes").get(property_type)
        )
        matches.extend(self.check(cfn, properties, specs, property_specs, path))

        return matches

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

        specs = (
            RESOURCE_SPECS.get(cfn.regions[0])
            .get("ResourceTypes")
            .get(resource_type, {})
            .get("Properties", {})
        )
        resource_specs = (
            RESOURCE_SPECS.get(cfn.regions[0]).get("ResourceTypes").get(resource_type)
        )
        matches.extend(self.check(cfn, properties, specs, resource_specs, path))

        return matches
