from unittest import TestCase

from parameterized import parameterized
from samtranslator.model.intrinsics import (
    get_logical_id_from_intrinsic,
    is_intrinsic,
    is_intrinsic_if,
    is_intrinsic_no_value,
    make_shorthand,
    validate_intrinsic_if_items,
)


class TestIntrinsics(TestCase):
    @parameterized.expand(["Ref", "Condition", "Fn::foo", "Fn::sub", "Fn::something"])
    def test_is_intrinsic_must_detect_intrinsics(self, intrinsic_name):
        input = {intrinsic_name: ["some value"]}

        self.assertTrue(is_intrinsic(input))

    def test_is_intrinsic_on_empty_input(self):
        self.assertFalse(is_intrinsic(None))

    def test_is_intrinsic_on_non_dict_input(self):
        self.assertFalse(is_intrinsic([1, 2, 3]))

    def test_is_intrinsic_on_intrinsic_like_dict_input(self):
        self.assertFalse(is_intrinsic({"Ref": "foo", "key": "bar"}))

    @parameterized.expand([({"Ref": "foo"}, "${foo}"), ({"Fn::GetAtt": ["foo", "Arn"]}, "${foo.Arn}")])
    def test_make_shorthand_success(self, input, expected):
        self.assertEqual(make_shorthand(input), expected)

    def test_make_short_hand_failure(self):
        input = {"Fn::Sub": "something"}

        with self.assertRaises(NotImplementedError):
            make_shorthand(input)

    def test_is_intrinsic_no_value_must_return_true_for_no_value(self):
        policy = {"Ref": "AWS::NoValue"}

        self.assertTrue(is_intrinsic_no_value(policy))

    def test_is_intrinsic_no_value_must_return_false_for_other_value(self):
        bad_key = {"sRefs": "AWS::NoValue"}
        bad_value = {"Ref": "SWA::NoValue"}
        too_many_keys = {"Ref": "AWS::NoValue", "feR": "SWA::NoValue"}

        self.assertFalse(is_intrinsic_no_value(bad_key))
        self.assertFalse(is_intrinsic_no_value(bad_value))
        self.assertFalse(is_intrinsic_no_value(None))
        self.assertFalse(is_intrinsic_no_value(too_many_keys))

    def test_is_intrinsic_if_must_return_true_for_if(self):
        policy = {"Fn::If": "some value"}

        self.assertTrue(is_intrinsic_if(policy))

    def test_is_intrinsic_if_must_return_false_for_others(self):
        too_many_keys = {"Fn::If": "some value", "Fn::And": "other value"}
        not_if = {"Fn::Or": "some value"}

        self.assertFalse(is_intrinsic_if(too_many_keys))
        self.assertFalse(is_intrinsic_if(not_if))
        self.assertFalse(is_intrinsic_if(None))

    def test_validate_intrinsic_if_items_valid(self):
        validate_intrinsic_if_items(["Condition", "Then", "Else"])

    def test_validate_intrinsic_if_items_invalid(self):
        not_enough_items = ["Then", "Else"]
        is_string = "Then"
        is_integer = 3
        is_boolean = True
        is_dict = {"Fn::If": "some value", "Fn::And": "other value"}

        self.assertRaises(ValueError, validate_intrinsic_if_items, not_enough_items)
        self.assertRaises(ValueError, validate_intrinsic_if_items, is_string)
        self.assertRaises(ValueError, validate_intrinsic_if_items, None)
        self.assertRaises(ValueError, validate_intrinsic_if_items, is_integer)
        self.assertRaises(ValueError, validate_intrinsic_if_items, is_boolean)
        self.assertRaises(ValueError, validate_intrinsic_if_items, is_dict)

    @parameterized.expand(
        [
            ({"Fn::GetAtt": ["Foo", "Bar"]}, "Foo"),
            ({"Fn::GetAtt": "Foo.Bar"}, "Foo"),
            ({"Ref": "Foo"}, "Foo"),
        ]
    )
    def test_get_logical_id_from_intrinsic_success(self, input, expected):
        self.assertEqual(get_logical_id_from_intrinsic(input), expected)

    @parameterized.expand(
        [
            (None,),
            ("Foo",),
            ({"Ref": True}),
            ({"Fn::GetAtt": "Foo"}),
            ({"Fn::GetAtt": ["Foo"]}),
            ({"Fn::GetAtt": [42, "Arn"]}),
            ({"Fn::If": ["Foo", "Bar"]}),
            ({"Fn::GetAtt": "Foo.Bar.WhatEverThisIs"},),
        ]
    )
    def test_get_logical_id_from_intrinsic_error(self, input):
        self.assertIsNone(get_logical_id_from_intrinsic(input))