# 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.statistic.statistic import OlcStatistic, IStatistic, StatisticSnapshot

class TestOlcStatistic(unittest.TestCase):
    """测试 OlcStatistic 的核心功能"""

    def setUp(self):
        self.statistic = OlcStatistic()

    def test_initial_thread_count(self):
        """初始并发数应为 0"""
        self.assertEqual(self.statistic.cur_thread_num(), 0)

    def test_initial_request_metrics(self):
        self.assertEqual(self.statistic.total_request_num(), 0)
        self.assertEqual(self.statistic.pass_request_num(), 0)
        self.assertEqual(self.statistic.block_request_num(), 0)
        self.assertEqual(self.statistic.pass_avg_latency_ms(), 0.0)
        self.assertEqual(self.statistic.pass_max_latency_ms(), 0.0)

    def test_request_counts(self):
        self.statistic.inc_pass_request_num()
        self.statistic.inc_pass_request_num()
        self.statistic.inc_pass_request_num()
        self.statistic.inc_block_request_num()

        self.assertEqual(self.statistic.total_request_num(), 4)
        self.assertEqual(self.statistic.pass_request_num(), 3)
        self.assertEqual(self.statistic.block_request_num(), 1)

    def test_pass_latency_metrics(self):
        self.statistic.inc_pass_request_num()
        self.statistic.record_pass_latency_ms(10.5)
        self.statistic.inc_pass_request_num()
        self.statistic.record_pass_latency_ms(20.5)

        self.assertEqual(self.statistic.pass_avg_latency_ms(), 15.5)
        self.assertEqual(self.statistic.pass_max_latency_ms(), 20.5)

    def test_negative_pass_latency_is_ignored(self):
        self.statistic.record_pass_latency_ms(-1)

        self.assertEqual(self.statistic.pass_avg_latency_ms(), 0.0)
        self.assertEqual(self.statistic.pass_max_latency_ms(), 0.0)

    def test_snapshot_returns_current_metrics(self):
        self.statistic.inc_thread_num()
        self.statistic.inc_pass_request_num()
        self.statistic.record_pass_latency_ms(10)
        self.statistic.inc_pass_request_num()
        self.statistic.record_pass_latency_ms(10)
        self.statistic.inc_pass_request_num()
        self.statistic.inc_block_request_num()
        self.statistic.record_pass_latency_ms(40.0)

        snapshot = self.statistic.snapshot()

        self.assertIsInstance(snapshot, StatisticSnapshot)
        self.assertEqual(snapshot.cur_thread_num, 1)
        self.assertEqual(snapshot.total_request_num_pre_second, 4)
        self.assertEqual(snapshot.pass_request_num_pre_second, 3)
        self.assertEqual(snapshot.block_request_num_pre_second, 1)
        self.assertEqual(snapshot.pass_avg_latency_ms_pre_second, 20.0)
        self.assertEqual(snapshot.pass_max_latency_ms_pre_second, 40.0)

    def test_snapshot_contains_dual_granularity(self):
        self.statistic.inc_pass_request_num()
        self.statistic.record_pass_latency_ms(5.0)
        self.statistic.inc_pass_request_num()
        self.statistic.record_pass_latency_ms(5.0)
        self.statistic.inc_block_request_num()
        snapshot = self.statistic.snapshot()

        self.assertEqual(snapshot.second_snap.total_request_num, 3)
        self.assertEqual(snapshot.second_snap.pass_request_num, 2)
        self.assertEqual(snapshot.second_snap.block_request_num, 1)
        self.assertEqual(snapshot.second_snap.pass_avg_latency_ms, 5.0)
        self.assertEqual(snapshot.second_snap.pass_max_latency_ms, 5.0)

        self.assertEqual(snapshot.custom_snap.total_request_num, 3)
        self.assertEqual(snapshot.custom_snap.pass_request_num, 2)
        self.assertEqual(snapshot.custom_snap.block_request_num, 1)
        self.assertEqual(snapshot.custom_snap.pass_avg_latency_ms, 5.0)
        self.assertEqual(snapshot.custom_snap.pass_max_latency_ms, 5.0)

    def test_inc_thread_num_once(self):
        """增加一次并发数"""
        self.statistic.inc_thread_num()
        self.assertEqual(self.statistic.cur_thread_num(), 1)

    def test_inc_thread_num_multiple(self):
        """多次增加并发数"""
        for _ in range(10):
            self.statistic.inc_thread_num()
        self.assertEqual(self.statistic.cur_thread_num(), 10)

    def test_dec_thread_num_after_inc(self):
        """先增加后减少,并发数应正确递减"""
        for _ in range(5):
            self.statistic.inc_thread_num()
        for _ in range(3):
            self.statistic.dec_thread_num()
        self.assertEqual(self.statistic.cur_thread_num(), 2)

    def test_dec_thread_num_to_zero(self):
        """递减到刚好 0"""
        self.statistic.inc_thread_num()
        self.statistic.dec_thread_num()
        self.assertEqual(self.statistic.cur_thread_num(), 0)

    def test_dec_thread_num_below_zero_should_clamp(self):
        """从 0 开始递减,应被限制在 0,不会变为负数"""
        self.statistic.dec_thread_num()
        self.assertEqual(self.statistic.cur_thread_num(), 0)

    def test_dec_thread_num_from_1_to_0_below_0(self):
        """从 1 递减 2 次,结果应为 0(被限制)"""
        self.statistic.inc_thread_num()
        self.statistic.dec_thread_num()
        self.statistic.dec_thread_num()
        self.assertEqual(self.statistic.cur_thread_num(), 0)

    def test_dec_thread_num_negative_accumulated(self):
        """多次从 0 递减,结果应始终为 0"""
        for _ in range(100):
            self.statistic.dec_thread_num()
        self.assertEqual(self.statistic.cur_thread_num(), 0)

    def test_large_inc_and_dec(self):
        """大量递增和递减后,并发数应正确"""
        for _ in range(1000):
            self.statistic.inc_thread_num()
        for _ in range(500):
            self.statistic.dec_thread_num()
        self.assertEqual(self.statistic.cur_thread_num(), 500)

    def test_concurrent_inc_thread_safety(self):
        """多线程并发递增,结果应正确累计"""

        def worker(stat, count):
            for _ in range(count):
                stat.inc_thread_num()

        threads = []
        thread_count = 10
        ops_per_thread = 100

        for _ in range(thread_count):
            t = threading.Thread(target=worker, args=(self.statistic, ops_per_thread))
            threads.append(t)
            t.start()

        for t in threads:
            t.join()

        self.assertEqual(self.statistic.cur_thread_num(), thread_count * ops_per_thread)

    def test_concurrent_request_metrics_thread_safety(self):
        def worker(stat, count):
            for _ in range(count):
                stat.inc_pass_request_num()
                stat.record_pass_latency_ms(1.5)

        threads = []
        thread_count = 10
        ops_per_thread = 100

        for _ in range(thread_count):
            t = threading.Thread(target=worker, args=(self.statistic, ops_per_thread))
            threads.append(t)
            t.start()

        for t in threads:
            t.join()

        total = thread_count * ops_per_thread
        self.assertEqual(self.statistic.total_request_num(), total)
        self.assertEqual(self.statistic.pass_request_num(), total)
        self.assertEqual(self.statistic.block_request_num(), 0)
        self.assertEqual(self.statistic.pass_avg_latency_ms(), 1.5)
        self.assertEqual(self.statistic.pass_max_latency_ms(), 1.5)

    def test_concurrent_mixed_ops_thread_safety(self):
        """多线程混合递增和递减,并发数不应低于 0"""

        results = []
        errors = []

        def worker_inc(stat, count):
            for _ in range(count):
                stat.inc_thread_num()

        def worker_dec(stat, count):
            for _ in range(count):
                stat.dec_thread_num()

        threads = []
        for _ in range(5):
            threads.append(threading.Thread(target=worker_inc, args=(self.statistic, 200)))
            threads.append(threading.Thread(target=worker_dec, args=(self.statistic, 200)))

        for t in threads:
            t.start()
        for t in threads:
            t.join()

        final_count = self.statistic.cur_thread_num()
        self.assertGreaterEqual(final_count, 0, "并发数不应低于 0")

    def test_concurrent_dec_thread_safety(self):
        """多线程并发递减(从 0 开始),验证不会变为负数"""
        threads = []

        def worker(stat, count):
            for _ in range(count):
                stat.dec_thread_num()

        for _ in range(10):
            t = threading.Thread(target=worker, args=(self.statistic, 100))
            threads.append(t)
            t.start()

        for t in threads:
            t.join()

        self.assertEqual(self.statistic.cur_thread_num(), 0)

    def test_multiple_instances_independence(self):
        """多个 OlcStatistic 实例应相互独立"""
        stat1 = OlcStatistic()
        stat2 = OlcStatistic()

        stat1.inc_thread_num()
        stat1.inc_thread_num()
        stat1.inc_thread_num()
        stat2.inc_thread_num()
        stat2.inc_thread_num()

        self.assertEqual(stat1.cur_thread_num(), 3)
        self.assertEqual(stat2.cur_thread_num(), 2)

    def test_interface_abstract_methods(self):
        """IStatistic 抽象方法不可直接实例化"""
        with self.assertRaises(TypeError):
            IStatistic()

    def test_inc_thread_num_return_type(self):
        """inc_thread_num 不应返回值"""
        result = self.statistic.inc_thread_num()
        self.assertIsNone(result)

    def test_dec_thread_num_return_type(self):
        """dec_thread_num 不应返回值"""
        result = self.statistic.dec_thread_num()
        self.assertIsNone(result)

    def test_cur_thread_num_return_type(self):
        """cur_thread_num 返回 int 类型"""
        result = self.statistic.cur_thread_num()
        self.assertIsInstance(result, int)


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