import json

import pytest

from moto.ecr.exceptions import InvalidParameterException
from moto.ecr.policy_validation import EcrLifecyclePolicyValidator


def test_validate():
    # given
    policy = {
        "rules": [
            {
                "rulePriority": 1,
                "description": "test policy",
                "selection": {
                    "tagStatus": "untagged",
                    "countType": "imageCountMoreThan",
                    "countNumber": 30,
                },
                "action": {"type": "expire"},
            },
            {
                "rulePriority": 2,
                "description": "test policy",
                "selection": {
                    "tagStatus": "tagged",
                    "tagPatternList": ["tagx"],
                    "tagPrefixList": ["3.9"],
                    "countType": "sinceImagePushed",
                    "countUnit": "days",
                    "countNumber": 30,
                },
                "action": {"type": "expire"},
            },
        ]
    }

    # when/then
    validator = EcrLifecyclePolicyValidator(json.dumps(policy))
    validator.validate()


@pytest.mark.parametrize(
    "policy",
    [
        "some invalid input",
        [
            {
                "rulePriority": 1,
                "description": "test policy",
                "selection": {
                    "tagStatus": "untagged",
                    "countType": "imageCountMoreThan",
                    "countNumber": 30,
                },
                "action": {"type": "expire"},
            }
        ],
    ],
    ids=["not_json", "not_dict"],
)
def test_validate_error_parse(policy):
    # given

    # when
    with pytest.raises(InvalidParameterException) as e:
        validator = EcrLifecyclePolicyValidator(json.dumps(policy))
        validator.validate()

    # then
    ex = e.value
    assert ex.code == "InvalidParameterException"
    assert (
        ex.message
        == "Invalid parameter at 'LifecyclePolicyText' failed to satisfy constraint: 'Lifecycle policy validation failure: Could not map policyString into LifecyclePolicy.'"
    )


@pytest.mark.parametrize(
    "policy",
    [
        {"no_rules": "test"},
        {
            "rules": {
                "rulePriority": 1,
                "description": "test policy",
                "selection": {
                    "tagStatus": "untagged",
                    "countType": "imageCountMoreThan",
                    "countNumber": 30,
                },
                "action": {"type": "expire"},
            },
        },
    ],
    ids=["no_rules", "not_list"],
)
def test_validate_error_extract_rules(policy):
    # given

    # when
    with pytest.raises(InvalidParameterException) as e:
        validator = EcrLifecyclePolicyValidator(json.dumps(policy))
        validator.validate()

    # then
    ex = e.value
    assert ex.code == "InvalidParameterException"
    assert (
        ex.message
        == "Invalid parameter at 'LifecyclePolicyText' failed to satisfy constraint: 'Lifecycle policy validation failure: object has missing required properties ([\"rules\"])'"
    )


@pytest.mark.parametrize("rule", ["not_dict"], ids=["not_dict"])
def test_validate_error_rule_type(rule):
    # given

    # when
    with pytest.raises(InvalidParameterException) as e:
        validator = EcrLifecyclePolicyValidator(json.dumps({"rules": [rule]}))
        validator.validate()

    # then
    ex = e.value
    assert ex.code == "InvalidParameterException"
    assert ex.message == (
        "Invalid parameter at 'LifecyclePolicyText' failed to satisfy constraint: "
        "'Lifecycle policy validation failure: "
        f'instance type ({type(rule)}) does not match any allowed primitive type (allowed: ["object"])\''
    )


@pytest.mark.parametrize(
    ["rule", "error_msg"],
    [
        [
            {
                "description": "test policy",
                "selection": {
                    "tagStatus": "untagged",
                    "countType": "imageCountMoreThan",
                    "countNumber": 30,
                },
                "action": {"type": "expire"},
            },
            'object has missing required properties (["rulePriority"])\'',
        ],
        [
            {
                "rulePriority": 1,
                "description": "test policy",
                "action": {"type": "expire"},
            },
            'object has missing required properties (["selection"])\'',
        ],
        [
            {
                "rulePriority": 1,
                "description": "test policy",
                "selection": {
                    "tagStatus": "untagged",
                    "countType": "imageCountMoreThan",
                    "countNumber": 30,
                },
            },
            'object has missing required properties (["action"])\'',
        ],
        [
            {
                "rulePriority": 1,
                "selection": {
                    "tagStatus": "untagged",
                    "countType": "imageCountMoreThan",
                    "countNumber": 30,
                },
                "action": {"type": "expire"},
                "unknown": 123,
            },
            'object instance has properties which are not allowed by the schema: (["unknown"])\'',
        ],
    ],
    ids=["missing_rulePriority", "missing_selection", "missing_action", "unknown"],
)
def test_validate_error_rule_properties(rule, error_msg):
    # given

    # when
    with pytest.raises(InvalidParameterException) as e:
        validator = EcrLifecyclePolicyValidator(json.dumps({"rules": [rule]}))
        validator.validate()

    # then
    ex = e.value
    assert ex.code == "InvalidParameterException"
    assert (
        ex.message
        == f"Invalid parameter at 'LifecyclePolicyText' failed to satisfy constraint: 'Lifecycle policy validation failure: {error_msg}"
    )


@pytest.mark.parametrize(
    ["action", "error_msg"],
    [
        [{}, 'object has missing required properties (["type"])\''],
        [
            {"type": "expire", "unknown": 123},
            (
                "object instance has properties "
                'which are not allowed by the schema: (["unknown"])\''
            ),
        ],
        [
            {"type": "keep"},
            (
                "instance value (keep) not found in enum "
                ':(possible values: ["expire"])\''
            ),
        ],
    ],
    ids=["missing_type", "unknown", "unknown_type_value"],
)
def test_validate_error_action_properties(action, error_msg):
    # given

    # when
    with pytest.raises(InvalidParameterException) as e:
        validator = EcrLifecyclePolicyValidator(
            json.dumps(
                {
                    "rules": [
                        {
                            "rulePriority": 1,
                            "selection": {
                                "tagStatus": "untagged",
                                "countType": "imageCountMoreThan",
                                "countNumber": 30,
                            },
                            "action": action,
                        }
                    ]
                }
            )
        )
        validator.validate()

    # then
    ex = e.value
    assert ex.code == "InvalidParameterException"
    assert (
        ex.message
        == f"Invalid parameter at 'LifecyclePolicyText' failed to satisfy constraint: 'Lifecycle policy validation failure: {error_msg}"
    )


@pytest.mark.parametrize(
    ["selection", "error_msg"],
    [
        [
            {"countType": "imageCountMoreThan", "countNumber": 30},
            'object has missing required properties (["tagStatus"])\'',
        ],
        [
            {"tagStatus": "untagged", "countNumber": 30},
            'object has missing required properties (["countType"])\'',
        ],
        [
            {"tagStatus": "untagged", "countType": "imageCountMoreThan"},
            'object has missing required properties (["countNumber"])\'',
        ],
        [
            {
                "tagStatus": "untagged",
                "countType": "imageCountMoreThan",
                "countNumber": 30,
                "unknown": 123,
            },
            (
                "object instance has properties "
                'which are not allowed by the schema: (["unknown"])\''
            ),
        ],
        [
            {
                "tagStatus": "unknown",
                "countType": "imageCountMoreThan",
                "countNumber": 30,
            },
            (
                "instance value (unknown) not found in enum "
                ':(possible values: ["any", "tagged", "untagged"])\''
            ),
        ],
        [
            {"tagStatus": "untagged", "countType": "unknown", "countNumber": 30},
            "instance failed to match exactly one schema (matched 0 out of 2)",
        ],
        [
            {
                "tagStatus": "untagged",
                "countType": "sinceImagePushed",
                "countUnit": "unknown",
                "countNumber": 30,
            },
            (
                "instance value (unknown) not found in enum "
                ':(possible values: ["days"])\''
            ),
        ],
        [
            {
                "tagStatus": "untagged",
                "countType": "imageCountMoreThan",
                "countNumber": 0,
            },
            (
                "numeric instance is lower than the required minimum "
                "(minimum: 1, found: 0)"
            ),
        ],
        [
            {
                "tagStatus": "untagged",
                "countType": "imageCountMoreThan",
                "countNumber": -1,
            },
            (
                "numeric instance is lower than the required minimum "
                "(minimum: 1, found: -1)"
            ),
        ],
    ],
    ids=[
        "missing_tagStatus",
        "missing_countType",
        "missing_countNumber",
        "unknown",
        "unknown_tagStatus_value",
        "unknown_countType_value",
        "unknown_countUnit_value",
        "zero_countNumber_value",
        "negative_countNumber_value",
    ],
)
def test_validate_error_selection_properties(selection, error_msg):
    # given

    # when
    with pytest.raises(InvalidParameterException) as e:
        validator = EcrLifecyclePolicyValidator(
            json.dumps(
                {
                    "rules": [
                        {
                            "rulePriority": 1,
                            "selection": selection,
                            "action": {"type": "expire"},
                        }
                    ]
                }
            )
        )
        validator.validate()

    # then
    ex = e.value
    assert ex.code == "InvalidParameterException"
    assert (
        ex.message
        == f"Invalid parameter at 'LifecyclePolicyText' failed to satisfy constraint: 'Lifecycle policy validation failure: {error_msg}"
    )
