// ---------------------------------------------------------------------------
// BT.Framework.Tests
// CoroutineRunnerTests.cs — CoroutineRunner PlayMode 테스트
// ---------------------------------------------------------------------------
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using BurningTimes.Core.Coroutine;

namespace BurningTimes.Tests.Core.Coroutine
{
    public class CoroutineRunnerTests
    {
        [SetUp] public void SetUp() => CoroutineRunner.ResetForTests();
        [TearDown] public void TearDown() => CoroutineRunner.ResetForTests();

        [UnityTest]
        public IEnumerator Start_Runs_To_Completion()
        {
            int counter = 0;
            var handle = CoroutineRunner.Start(CountRoutine(3, () => counter++));
            Assert.IsTrue(handle.IsValid);

            while (CoroutineRunner.IsRunning(handle)) yield return null;
            Assert.AreEqual(3, counter);
        }

        [UnityTest]
        public IEnumerator Stop_Prevents_Completion()
        {
            int counter = 0;
            var handle = CoroutineRunner.Start(CountRoutine(10, () => counter++));
            yield return null; yield return null;
            CoroutineRunner.Stop(handle);

            int snapshot = counter;
            for (int i = 0; i < 5; i++) yield return null;

            Assert.IsFalse(CoroutineRunner.IsRunning(handle));
            Assert.AreEqual(snapshot, counter, "중단 후에는 counter가 증가하지 않아야 한다");
        }

        [UnityTest]
        public IEnumerator Pause_Then_Resume_Continues()
        {
            int counter = 0;
            var handle = CoroutineRunner.Start(CountRoutine(10, () => counter++));
            yield return null; yield return null;

            CoroutineRunner.Pause(handle);
            int paused = counter;
            for (int i = 0; i < 5; i++) yield return null;
            Assert.AreEqual(paused, counter, "일시정지 중에는 진행이 없어야 한다");

            CoroutineRunner.Resume(handle);
            while (CoroutineRunner.IsRunning(handle)) yield return null;
            Assert.AreEqual(10, counter);
        }

        [UnityTest]
        public IEnumerator Duplicate_Replace_Stops_Previous()
        {
            int a = 0, b = 0;
            var h1 = CoroutineRunner.Start("k", CountRoutine(20, () => a++));
            yield return null;
            var h2 = CoroutineRunner.Start("k", CountRoutine(5, () => b++), DuplicatePolicy.Replace);

            Assert.IsFalse(CoroutineRunner.IsRunning(h1), "첫 핸들은 중단되어야 한다");
            while (CoroutineRunner.IsRunning(h2)) yield return null;
            Assert.AreEqual(5, b);
            Assert.Less(a, 20, "첫 루틴은 20회를 채우지 못했어야 한다");
        }

        [UnityTest]
        public IEnumerator Duplicate_Ignore_Keeps_Original()
        {
            int a = 0, b = 0;
            var h1 = CoroutineRunner.Start("k", CountRoutine(5, () => a++));
            var h2 = CoroutineRunner.Start("k", CountRoutine(5, () => b++), DuplicatePolicy.Ignore);

            Assert.AreEqual(h1.Id, h2.Id, "Ignore는 기존 핸들을 반환해야 한다");
            while (CoroutineRunner.IsRunning(h1)) yield return null;
            Assert.AreEqual(5, a);
            Assert.AreEqual(0, b, "Ignore된 루틴은 실행되지 않아야 한다");
        }

        [UnityTest]
        public IEnumerator StopByKey_Works()
        {
            int counter = 0;
            CoroutineRunner.Start("kill-me", CountRoutine(20, () => counter++));
            yield return null; yield return null;
            Assert.IsTrue(CoroutineRunner.IsRunningByKey("kill-me"));

            CoroutineRunner.StopByKey("kill-me");
            Assert.IsFalse(CoroutineRunner.IsRunningByKey("kill-me"));
        }

        [UnityTest]
        public IEnumerator Exception_In_Routine_Is_Captured()
        {
            LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex(@"\[Coroutine\] coroutine threw"));
            LogAssert.Expect(LogType.Exception, new System.Text.RegularExpressions.Regex("boom"));

            var handle = CoroutineRunner.Start(ThrowingRoutine());
            while (CoroutineRunner.IsRunning(handle)) yield return null;

            // 완료(예외 종료) 후 내부 정리가 되어 IsRunning이 false여야 한다
            Assert.IsFalse(CoroutineRunner.IsRunning(handle));
        }

        // --------- helper routines ---------

        private static IEnumerator CountRoutine(int times, System.Action onTick)
        {
            for (int i = 0; i < times; i++)
            {
                onTick?.Invoke();
                yield return null;
            }
        }

        private static IEnumerator ThrowingRoutine()
        {
            yield return null;
            throw new System.InvalidOperationException("boom");
        }
    }
}
