# 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.

"""重构核心契约验证:业务/链路分离后,
即使某个 handler 漏覆盖 async_do_in_coming,**后续 handler 的 async 路径仍然正常**。

修复后:
  A.async_in_coming → B.async_in_coming(基类提供,永远 await async_next)
    → B.async_do_in_coming(默认委托,仅 B 的业务退化为同步)
    → C.async_in_coming → ... 所有后续 handler 的 async 路径正常工作
"""
import asyncio
import unittest

from olc.control.handler_chain.box_handler import AbsBoxHandler
from olc.control.handler_chain.box_handler_chain_builder import (
    DefaultBoxHandlerChain,
)


def _arun(coro):
    return asyncio.new_event_loop().run_until_complete(coro)


class _RecordingHandler(AbsBoxHandler):
    """同时覆盖 sync + async 业务,并记录走了哪条路"""

    def __init__(self, name):
        super().__init__()
        self.name = name
        self.sync_calls = 0
        self.async_calls = 0

    def do_in_coming(self, context, groups):
        self.sync_calls += 1
        return None

    async def async_do_in_coming(self, context, groups):
        self.async_calls += 1
        return None

    def do_out_coming(self, context):
        pass


class _LegacySyncOnlyHandler(AbsBoxHandler):
    """模拟"老用户写的 handler":只覆盖 sync 业务,没覆盖 async 业务"""

    def __init__(self, name):
        super().__init__()
        self.name = name
        self.sync_calls = 0

    def do_in_coming(self, context, groups):
        self.sync_calls += 1
        return None

    # ★ 故意不覆盖 async_do_in_coming,让其走基类默认委托(→ sync do_in_coming)

    def do_out_coming(self, context):
        pass


class _Ctx:
    """最小 mock context(不依赖完整 OlcContext)"""

    def __init__(self):
        self.stop = False


class TestNoChainBreakOnPartialAsyncOverride(unittest.TestCase):
    """v3 重构核心契约:链路上任意 handler 漏覆盖 async_do_ 不会断链"""

    def test_legacy_handler_in_middle_does_not_break_downstream(self):
        """A(async ok) → B(sync-only, 漏覆盖 async_do_) → C(async ok)
        v2: C 的 async 路径会被 B 的同步链路绕过
        v3: C 仍然正常走 async_do_in_coming
        """
        chain = DefaultBoxHandlerChain()
        a = _RecordingHandler("A")
        b = _LegacySyncOnlyHandler("B")
        c = _RecordingHandler("C")
        chain.add_last_box(a).add_last_box(b).add_last_box(c)

        _arun(chain.async_in_coming(_Ctx(), []))

        # A 应该走 async 路径
        self.assertEqual(a.async_calls, 1, "A 的 async 业务应被调用")
        self.assertEqual(a.sync_calls, 0, "A 不应该走 sync 路径")

        # B 走 async_in_coming(基类提供)→ 默认委托到 sync do_in_coming
        self.assertEqual(b.sync_calls, 1, "B 的 sync 业务作为默认委托被调用")

        # ★ 关键断言:C 仍然走 async 路径(不会因为 B 漏覆盖而断链)
        self.assertEqual(c.async_calls, 1, "C 的 async 业务必须正常被调用 —— v3 重构核心保障")
        self.assertEqual(c.sync_calls, 0, "C 绝不应被强制走 sync 路径")

    def test_multiple_legacy_handlers_do_not_break_each_other(self):
        """A(sync-only) → B(sync-only) → C(async ok)
        三个 handler 中前两个都漏覆盖,C 仍然必须走 async
        """
        chain = DefaultBoxHandlerChain()
        a = _LegacySyncOnlyHandler("A")
        b = _LegacySyncOnlyHandler("B")
        c = _RecordingHandler("C")
        chain.add_last_box(a).add_last_box(b).add_last_box(c)

        _arun(chain.async_in_coming(_Ctx(), []))

        self.assertEqual(a.sync_calls, 1)
        self.assertEqual(b.sync_calls, 1)
        self.assertEqual(c.async_calls, 1, "即使前面两个都漏覆盖,C 仍走 async")
        self.assertEqual(c.sync_calls, 0)


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