# Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.
# MindIE 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
import threading

from olc.bean.tag_group import TagGroup
from olc.statistic.statistic import IStatistic, OlcStatistic, StatisticSnapshot
from olc.statistic.statistic_factory import OlcStatisticFactory


class TestOlcStatisticFactory(unittest.TestCase):
    """测试 OlcStatisticFactory 的核心功能"""

    def setUp(self):
        OlcStatisticFactory._instance = None
        self.factory = OlcStatisticFactory.get_instance()

    def tearDown(self):
        OlcStatisticFactory._instance = None

    def test_singleton_pattern(self):
        """单例模式:多次调用 get_instance 应返回同一实例"""
        factory1 = OlcStatisticFactory.get_instance()
        factory2 = OlcStatisticFactory.get_instance()
        self.assertIs(factory1, factory2)

    def test_get_statistic_returns_none_for_uncreated(self):
        """对于未创建的 tag_group,get_statistic 返回 None"""
        tag_group = TagGroup(domain="test", name="nonexistent")
        result = self.factory.get_statistic(tag_group)
        self.assertIsNone(result)

    def test_get_statistic_with_none_tag_group(self):
        """传入 None 的 tag_group,get_statistic 返回 None"""
        result = self.factory.get_statistic(None)
        self.assertIsNone(result)

    def test_get_and_create_returns_statistic(self):
        """get_and_create 应返回 IStatistic 实例"""
        tag_group = TagGroup(domain="test", name="create1")
        result = self.factory.get_and_create(tag_group)
        self.assertIsInstance(result, IStatistic)
        self.assertIsInstance(result, OlcStatistic)

    def test_get_and_create_creates_new_instance_per_tag_group(self):
        """不同的 tag_group 应创建不同的实例"""
        tg1 = TagGroup(domain="a", name="1")
        tg2 = TagGroup(domain="a", name="2")
        stat1 = self.factory.get_and_create(tg1)
        stat2 = self.factory.get_and_create(tg2)
        self.assertIsNot(stat1, stat2)

    def test_get_and_create_returns_same_instance_for_same_tag_group(self):
        """相同的 tag_group 应返回缓存的同一实例"""
        tag_group = TagGroup(domain="test", name="same")
        stat1 = self.factory.get_and_create(tag_group)
        stat2 = self.factory.get_and_create(tag_group)
        self.assertIs(stat1, stat2)

    def test_get_statistic_returns_cached_instance(self):
        """get_and_create 后 get_statistic 应返回同一实例"""
        tag_group = TagGroup(domain="test", name="cached")
        created = self.factory.get_and_create(tag_group)
        retrieved = self.factory.get_statistic(tag_group)
        self.assertIs(created, retrieved)

    def test_get_and_create_with_none_tag_group_raises(self):
        """get_and_create 传入 None 应抛出 ValueError"""
        with self.assertRaises(ValueError) as ctx:
            self.factory.get_and_create(None)
        self.assertIn("tag_group", str(ctx.exception))

    def test_get_and_create_idempotent(self):
        """多次 get_and_create 同一 tag_group 应返回同一实例"""
        tag_group = TagGroup(domain="test", name="idempotent")
        results = [self.factory.get_and_create(tag_group) for _ in range(10)]
        for i in range(1, len(results)):
            self.assertIs(results[0], results[i])

    def test_tag_group_equality_as_cache_key(self):
        """相同的 domain + name 的 TagGroup 应命中同一缓存"""
        tg1 = TagGroup(domain="test", name="eq")
        tg2 = TagGroup(domain="test", name="eq")
        stat1 = self.factory.get_and_create(tg1)
        stat2 = self.factory.get_and_create(tg2)
        self.assertIs(stat1, stat2)

    def test_tag_group_different_domain_same_name(self):
        """不同 domain 但相同 name 的 TagGroup 应视为不同的缓存项"""
        tg1 = TagGroup(domain="a", name="same_name")
        tg2 = TagGroup(domain="b", name="same_name")
        stat1 = self.factory.get_and_create(tg1)
        stat2 = self.factory.get_and_create(tg2)
        self.assertIsNot(stat1, stat2)

    def test_subscribe_clean_clears_cache(self):
        """subscribe_clean 应清空所有缓存"""
        tg1 = TagGroup(domain="test", name="clean1")
        tg2 = TagGroup(domain="test", name="clean2")
        self.factory.get_and_create(tg1)
        self.factory.get_and_create(tg2)

        self.factory.subscribe_clean(None)

        self.assertIsNone(self.factory.get_statistic(tg1))
        self.assertIsNone(self.factory.get_statistic(tg2))

    def test_after_clean_get_and_create_works(self):
        """清空后再次 get_and_create 应创建新实例"""
        tag_group = TagGroup(domain="test", name="recreate")
        stat1 = self.factory.get_and_create(tag_group)
        self.factory.subscribe_clean(None)
        stat2 = self.factory.get_and_create(tag_group)

        self.assertIsNot(stat1, stat2)
        self.assertIsInstance(stat2, OlcStatistic)

    def test_statistic_operations_after_factory_retrieval(self):
        """通过工厂获取的统计实例应能正常操作"""
        tag_group = TagGroup(domain="test", name="ops")
        stat = self.factory.get_and_create(tag_group)

        stat.inc_thread_num()
        stat.inc_thread_num()
        stat.dec_thread_num()
        self.assertEqual(stat.cur_thread_num(), 1)

        stat.dec_thread_num()
        stat.dec_thread_num()
        self.assertEqual(stat.cur_thread_num(), 0)

    def test_concurrent_get_and_create_same_tag_group(self):
        """多线程并发 get_and_create 同一 tag_group,应只创建一个实例"""
        tag_group = TagGroup(domain="test", name="concurrent_create")
        results = []

        def worker():
            for _ in range(50):
                stat = self.factory.get_and_create(tag_group)
                results.append(stat)

        threads = [threading.Thread(target=worker) for _ in range(10)]
        for t in threads:
            t.start()
        for t in threads:
            t.join()

        first = results[0]
        for r in results:
            self.assertIs(r, first)

    def test_concurrent_get_and_create_different_tag_groups(self):
        """多线程并发创建不同 tag_group,所有实例应不同"""
        results = {}

        def worker(domain_suffix):
            tg = TagGroup(domain="test", name=f"concurrent_{domain_suffix}")
            stat = self.factory.get_and_create(tg)
            results[domain_suffix] = stat

        threads = [threading.Thread(target=worker, args=(i,)) for i in range(50)]
        for t in threads:
            t.start()
        for t in threads:
            t.join()

        unique_instances = set(id(v) for v in results.values())
        self.assertEqual(len(unique_instances), 50)

    def test_cache_ttl_eviction(self):
        """等待 TTL 过期后,缓存的统计实例应被清理"""
        tag_group = TagGroup(domain="test", name="ttl")
        old_factory = OlcStatisticFactory.get_instance()
        stat = old_factory.get_and_create(tag_group)
        retrieved = old_factory.get_statistic(tag_group)
        self.assertIs(stat, retrieved)

    def test_get_statistic_does_not_create(self):
        """get_statistic 不应创建新的统计实例"""
        tag_group = TagGroup(domain="test", name="nope")
        result = self.factory.get_statistic(tag_group)
        self.assertIsNone(result)

    def test_get_all_snapshots_returns_tag_group_snapshot_dict(self):
        """get_all_snapshots 应返回 TagGroup 到 StatisticSnapshot 的映射"""
        tag_group1 = TagGroup(domain="test", name="snap1")
        tag_group2 = TagGroup(domain="test", name="snap2")
        statistic1 = self.factory.get_and_create(tag_group1)
        statistic2 = self.factory.get_and_create(tag_group2)

        statistic1.inc_thread_num()
        statistic1.inc_pass_request_num()
        statistic1.record_pass_latency_ms(12.0)
        statistic2.inc_block_request_num()

        snapshots = self.factory.get_all_snapshots()

        self.assertIsInstance(snapshots, dict)
        self.assertIn(tag_group1, snapshots)
        self.assertIn(tag_group2, snapshots)
        self.assertIsInstance(snapshots[tag_group1], StatisticSnapshot)
        self.assertIsInstance(snapshots[tag_group2], StatisticSnapshot)
        self.assertEqual(snapshots[tag_group1].cur_thread_num, 1)
        self.assertEqual(snapshots[tag_group1].total_request_num_pre_second, 1)
        self.assertEqual(snapshots[tag_group1].pass_request_num_pre_second, 1)
        self.assertEqual(snapshots[tag_group1].block_request_num_pre_second, 0)
        self.assertEqual(snapshots[tag_group2].total_request_num_pre_second, 1)
        self.assertEqual(snapshots[tag_group2].pass_request_num_pre_second, 0)
        self.assertEqual(snapshots[tag_group2].block_request_num_pre_second, 1)


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