# Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.
# OpenOLC is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#         `http://license.coscl.org.cn/MulanPSL2`
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.

import unittest
from unittest.mock import patch, MagicMock

from olc.bean.olc_control_request import OlcControlRequest
from olc.bean.tag_group import TagGroup, OlcTag
from olc.control.matcher.group_matcher import GroupMatcher
from olc.rule.olc_rule_manager import OlcRuleManager


class _FakeTypingList:
    def __getitem__(self, item):
        return list


class TestGroupMatcher(unittest.TestCase):

    def setUp(self):
        with patch("olc.control.matcher.group_matcher.Event"):
            self.matcher = GroupMatcher()
        self.matcher._cache = MagicMock()
        self._list_patcher = patch(
            "olc.control.matcher.group_matcher.List", _FakeTypingList()
        )
        self._list_patcher.start()

    def tearDown(self):
        self._list_patcher.stop()

    def test_match_tag_groups_cache_hit(self):
        request = OlcControlRequest(tags={"env": "prod"})
        cached_groups = [TagGroup(domain="test", name="group1")]
        self.matcher._cache.get.return_value = cached_groups

        with patch.object(OlcRuleManager, "get_instance") as mock_mgr:
            result = self.matcher.match_tag_groups(request)

        self.assertEqual(result, cached_groups)
        self.matcher._cache.get.assert_called_once_with(request)

    def test_match_tag_groups_cache_hit_refreshes_sub_group(self):
        request = OlcControlRequest(tags={"env": "prod"})
        cached_groups = [TagGroup(domain="test", name="group1")]
        self.matcher._cache.get.return_value = cached_groups

        with patch.object(OlcRuleManager, "get_instance") as mock_mgr:
            result = self.matcher.match_tag_groups(request)

        mock_mgr.return_value.refresh_sub_group.assert_called_once_with(cached_groups)
        self.assertEqual(result, cached_groups)

    def test_match_tag_groups_parent_groups_none(self):
        request = OlcControlRequest(tags={"env": "prod"})
        self.matcher._cache.get.return_value = None

        with patch.object(OlcRuleManager, "get_instance") as mock_mgr:
            mock_mgr.return_value.get_group_exclude_sub_group.return_value = None
            result = self.matcher.match_tag_groups(request)

        self.assertIsNone(result)

    def test_match_tag_groups_with_parent_groups_no_sub_group(self):
        request = OlcControlRequest(tags={"env": "prod"})
        self.matcher._cache.get.return_value = None

        parent_group = TagGroup(domain="test", name="group1", tags=[])
        parent_group.create_sub_group = MagicMock(return_value=False)

        with patch.object(OlcRuleManager, "get_instance") as mock_mgr:
            mock_mgr.return_value.get_group_exclude_sub_group.return_value = [
                parent_group
            ]
            with patch.object(
                self.matcher, "_GroupMatcher__match_tag_group", return_value=True
            ):
                result = self.matcher.match_tag_groups(request)

        self.assertIsNotNone(result)
        self.assertIn(parent_group, result)

    def test_match_tag_groups_with_sub_group_creation_dup_exists(self):
        request = OlcControlRequest(tags={"env": "prod"})
        self.matcher._cache.get.return_value = None

        tag = OlcTag(match="equal", tag="env", value="prod", share=False)
        parent_group = TagGroup(domain="test", name="group1", tags=[tag])
        parent_group.create_sub_group = MagicMock(return_value=True)

        cached_group = TagGroup(domain="test", name="group1$$env@prod")

        with patch.object(OlcRuleManager, "get_instance") as mock_mgr:
            mock_mgr.return_value.get_group_exclude_sub_group.return_value = [
                parent_group
            ]
            mock_mgr.return_value.is_group_policy_dup.return_value = True
            mock_mgr.return_value.get_group_from_cache.return_value = cached_group
            with patch.object(
                self.matcher, "_GroupMatcher__match_tag_group", return_value=True
            ):
                result = self.matcher.match_tag_groups(request)

        self.assertIsNotNone(result)
        self.assertIn(cached_group, result)

    def test_match_tag_groups_with_sub_group_creation_not_dup(self):
        request = OlcControlRequest(tags={"env": "prod"})
        self.matcher._cache.get.return_value = None

        tag = OlcTag(match="equal", tag="env", value="prod", share=False)
        parent_group = TagGroup(domain="test", name="group1", tags=[tag])
        parent_group.create_sub_group = MagicMock(return_value=True)

        new_group = TagGroup(domain="test", name="group1$$env@prod")

        with patch.object(OlcRuleManager, "get_instance") as mock_mgr:
            mock_mgr.return_value.get_group_exclude_sub_group.return_value = [
                parent_group
            ]
            mock_mgr.return_value.is_group_policy_dup.return_value = False
            mock_mgr.return_value.get_group_from_cache.return_value = new_group
            with patch.object(
                self.matcher, "_GroupMatcher__match_tag_group", return_value=True
            ):
                result = self.matcher.match_tag_groups(request)

        self.assertIsNotNone(result)
        self.assertIn(new_group, result)

    def test_match_tag_groups_with_sub_group_creation_not_dup_group_none(self):
        request = OlcControlRequest(tags={"env": "prod"})
        self.matcher._cache.get.return_value = None

        tag = OlcTag(match="equal", tag="env", value="prod", share=False)
        parent_group = TagGroup(domain="test", name="group1", tags=[tag])
        parent_group.create_sub_group = MagicMock(return_value=True)

        with patch.object(OlcRuleManager, "get_instance") as mock_mgr:
            mock_mgr.return_value.get_group_exclude_sub_group.return_value = [
                parent_group
            ]
            mock_mgr.return_value.is_group_policy_dup.return_value = False
            mock_mgr.return_value.get_group_from_cache.return_value = None
            with patch.object(
                self.matcher, "_GroupMatcher__match_tag_group", return_value=True
            ):
                result = self.matcher.match_tag_groups(request)

        self.assertIsNotNone(result)
        self.assertIn(parent_group, result)

    def test_match_tag_groups_caches_result(self):
        request = OlcControlRequest(tags={"env": "prod"})
        self.matcher._cache.get.return_value = None

        parent_group = TagGroup(domain="test", name="group1", tags=[])
        parent_group.create_sub_group = MagicMock(return_value=False)

        with patch.object(OlcRuleManager, "get_instance") as mock_mgr:
            mock_mgr.return_value.get_group_exclude_sub_group.return_value = [
                parent_group
            ]
            with patch.object(
                self.matcher, "_GroupMatcher__match_tag_group", return_value=True
            ):
                result = self.matcher.match_tag_groups(request)

        self.matcher._cache.put.assert_called_once()

    def test_match_tag_groups_does_not_cache_empty_result(self):
        request = OlcControlRequest(tags={"env": "prod"})
        self.matcher._cache.get.return_value = None

        with patch.object(OlcRuleManager, "get_instance") as mock_mgr:
            mock_mgr.return_value.get_group_exclude_sub_group.return_value = []
            result = self.matcher.match_tag_groups(request)

        self.matcher._cache.put.assert_not_called()

    def test___match_tag_group_all_tags_match(self):
        request = OlcControlRequest(tags={"env": "prod", "region": "us"})
        tag1 = OlcTag(match="equal", tag="env", value="prod")
        tag2 = OlcTag(match="equal", tag="region", value="us")
        parent_group = TagGroup(domain="test", name="group1", tags=[tag1, tag2])

        with patch(
            "olc.control.matcher.group_matcher.OperationMatcherFactory"
        ) as mock_factory:
            mock_matcher = MagicMock()
            mock_matcher.match.return_value = True
            mock_factory.get_operation.return_value = mock_matcher
            result = self.matcher._GroupMatcher__match_tag_group(request, parent_group)

        self.assertTrue(result)

    def test___match_tag_group_one_tag_not_match(self):
        request = OlcControlRequest(tags={"env": "prod", "region": "us"})
        tag1 = OlcTag(match="equal", tag="env", value="prod")
        tag2 = OlcTag(match="equal", tag="region", value="eu")
        parent_group = TagGroup(domain="test", name="group1", tags=[tag1, tag2])

        with patch(
            "olc.control.matcher.group_matcher.OperationMatcherFactory"
        ) as mock_factory:
            mock_matcher1 = MagicMock()
            mock_matcher1.match.return_value = True
            mock_matcher2 = MagicMock()
            mock_matcher2.match.return_value = False
            mock_factory.get_operation.side_effect = [mock_matcher1, mock_matcher2]
            result = self.matcher._GroupMatcher__match_tag_group(request, parent_group)

        self.assertFalse(result)

    def test___match_tag_group_matcher_none(self):
        request = OlcControlRequest(tags={"env": "prod"})
        tag1 = OlcTag(match="unknown", tag="env", value="prod")
        parent_group = TagGroup(domain="test", name="group1", tags=[tag1])

        with patch(
            "olc.control.matcher.group_matcher.OperationMatcherFactory"
        ) as mock_factory:
            mock_factory.get_operation.return_value = None
            result = self.matcher._GroupMatcher__match_tag_group(request, parent_group)

        self.assertFalse(result)

    def test___match_tag_group_empty_tags(self):
        request = OlcControlRequest(tags={"env": "prod"})
        parent_group = TagGroup(domain="test", name="group1", tags=[])

        with patch(
            "olc.control.matcher.group_matcher.OperationMatcherFactory"
        ) as mock_factory:
            result = self.matcher._GroupMatcher__match_tag_group(request, parent_group)

        self.assertTrue(result)
        mock_factory.get_operation.assert_not_called()

    def test_get_and_refresh_cache_hit(self):
        request = OlcControlRequest(tags={"env": "prod"})
        cached_groups = [TagGroup(domain="test", name="group1")]
        self.matcher._cache.get.return_value = cached_groups

        with patch.object(OlcRuleManager, "get_instance") as mock_mgr:
            result = self.matcher._get_and_refresh(request)

        self.assertEqual(result, cached_groups)
        mock_mgr.return_value.refresh_sub_group.assert_called_once_with(cached_groups)

    def test_get_and_refresh_cache_miss(self):
        request = OlcControlRequest(tags={"env": "prod"})
        self.matcher._cache.get.return_value = None

        result = self.matcher._get_and_refresh(request)

        self.assertIsNone(result)

    def test_clean_cache(self):
        self.matcher._cache = MagicMock()
        self.matcher.clean_cache(sender=None)
        self.matcher._cache.clear.assert_called_once()

    def test_match_tag_groups_skip_non_matching_parent(self):
        request = OlcControlRequest(tags={"env": "prod"})
        self.matcher._cache.get.return_value = None

        parent_group = TagGroup(domain="test", name="group1", tags=[])

        with patch.object(OlcRuleManager, "get_instance") as mock_mgr:
            mock_mgr.return_value.get_group_exclude_sub_group.return_value = [
                parent_group
            ]
            with patch.object(
                self.matcher, "_GroupMatcher__match_tag_group", return_value=False
            ):
                result = self.matcher.match_tag_groups(request)

        self.assertIsNotNone(result)
        self.assertEqual(len(result), 0)

    def test_match_tag_groups_multiple_parent_groups(self):
        request = OlcControlRequest(tags={"env": "prod"})
        self.matcher._cache.get.return_value = None

        parent1 = TagGroup(domain="test", name="group1", tags=[])
        parent2 = TagGroup(domain="test", name="group2", tags=[])
        parent1.create_sub_group = MagicMock(return_value=False)
        parent2.create_sub_group = MagicMock(return_value=False)

        with patch.object(OlcRuleManager, "get_instance") as mock_mgr:
            mock_mgr.return_value.get_group_exclude_sub_group.return_value = [
                parent1,
                parent2,
            ]
            with patch.object(
                self.matcher, "_GroupMatcher__match_tag_group", return_value=True
            ):
                result = self.matcher.match_tag_groups(request)

        self.assertIsNotNone(result)
        self.assertEqual(len(result), 2)

    def test_match_tag_groups_with_sub_group_dup_and_group_none(self):
        request = OlcControlRequest(tags={"env": "prod"})
        self.matcher._cache.get.return_value = None

        tag = OlcTag(match="equal", tag="env", value="prod", share=False)
        parent_group = TagGroup(domain="test", name="group1", tags=[tag])
        parent_group.create_sub_group = MagicMock(return_value=True)

        with patch.object(OlcRuleManager, "get_instance") as mock_mgr:
            mock_mgr.return_value.get_group_exclude_sub_group.return_value = [
                parent_group
            ]
            mock_mgr.return_value.is_group_policy_dup.return_value = True
            mock_mgr.return_value.get_group_from_cache.return_value = None
            with patch.object(
                self.matcher, "_GroupMatcher__match_tag_group", return_value=True
            ):
                result = self.matcher.match_tag_groups(request)

        self.assertIsNotNone(result)
        self.assertIn(parent_group, result)

    def test_match_tag_groups_empty_parent_groups_list(self):
        request = OlcControlRequest(tags={"env": "prod"})
        self.matcher._cache.get.return_value = None

        with patch.object(OlcRuleManager, "get_instance") as mock_mgr:
            mock_mgr.return_value.get_group_exclude_sub_group.return_value = []
            result = self.matcher.match_tag_groups(request)

        self.assertIsNotNone(result)
        self.assertEqual(len(result), 0)


if __name__ == "__main__":
    unittest.main()