// ---------------------------------------------------------------------------
// BT.Framework.Tests
// LogTests.cs — Log 중앙 로거 단위 테스트
// ---------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using NUnit.Framework;
using BurningTimes.Core.Util.Log;

namespace BurningTimes.Tests.Core.Util.Log
{
    public class LogTests
    {
        private sealed class RecordingSink : ILogSink
        {
            public readonly List<(LogLevel level, string category, string message, Exception exception)> Events
                = new List<(LogLevel, string, string, Exception)>();

            public void Emit(LogLevel level, string category, string message, Exception exception)
                => Events.Add((level, category, message, exception));
        }

        private RecordingSink _sink;
        private LogLevel _savedMinLevel;
        private bool _savedPrefix;

        [SetUp]
        public void SetUp()
        {
            _savedMinLevel = global::BurningTimes.Core.Util.Log.Log.MinLevel;
            _savedPrefix = global::BurningTimes.Core.Util.Log.Log.PrefixCategory;
            global::BurningTimes.Core.Util.Log.Log.ClearSinks();
            global::BurningTimes.Core.Util.Log.Log.ResetCategoryFilter();
            global::BurningTimes.Core.Util.Log.Log.MinLevel = LogLevel.Verbose;
            global::BurningTimes.Core.Util.Log.Log.PrefixCategory = true;

            _sink = new RecordingSink();
            global::BurningTimes.Core.Util.Log.Log.AddSink(_sink);
        }

        [TearDown]
        public void TearDown()
        {
            global::BurningTimes.Core.Util.Log.Log.ClearSinks();
            global::BurningTimes.Core.Util.Log.Log.ResetCategoryFilter();
            global::BurningTimes.Core.Util.Log.Log.MinLevel = _savedMinLevel;
            global::BurningTimes.Core.Util.Log.Log.PrefixCategory = _savedPrefix;
        }

        [Test]
        public void Warn_Sink_Receives_Formatted_Message()
        {
            UnityEngine.TestTools.LogAssert.Expect(UnityEngine.LogType.Warning, "[Net] timeout");
            global::BurningTimes.Core.Util.Log.Log.Warn("Net", "timeout");

            Assert.AreEqual(1, _sink.Events.Count);
            Assert.AreEqual(LogLevel.Warn, _sink.Events[0].level);
            Assert.AreEqual("Net", _sink.Events[0].category);
            Assert.AreEqual("[Net] timeout", _sink.Events[0].message);
            Assert.IsNull(_sink.Events[0].exception);
        }

        [Test]
        public void MinLevel_Filters_Below()
        {
            global::BurningTimes.Core.Util.Log.Log.MinLevel = LogLevel.Error;

            global::BurningTimes.Core.Util.Log.Log.Warn("Net", "below");

            UnityEngine.TestTools.LogAssert.Expect(UnityEngine.LogType.Error, "[Net] above");
            global::BurningTimes.Core.Util.Log.Log.Error("Net", "above");

            Assert.AreEqual(1, _sink.Events.Count);
            Assert.AreEqual(LogLevel.Error, _sink.Events[0].level);
        }

        [Test]
        public void Disabled_Category_Is_Skipped()
        {
            global::BurningTimes.Core.Util.Log.Log.DisableCategory("Net");
            global::BurningTimes.Core.Util.Log.Log.Warn("Net", "blocked");

            UnityEngine.TestTools.LogAssert.Expect(UnityEngine.LogType.Warning, "[UI] kept");
            global::BurningTimes.Core.Util.Log.Log.Warn("UI", "kept");

            Assert.AreEqual(1, _sink.Events.Count);
            Assert.AreEqual("UI", _sink.Events[0].category);
        }

        [Test]
        public void PrefixCategory_Off_Omits_Tag()
        {
            global::BurningTimes.Core.Util.Log.Log.PrefixCategory = false;

            UnityEngine.TestTools.LogAssert.Expect(UnityEngine.LogType.Warning, "no-tag");
            global::BurningTimes.Core.Util.Log.Log.Warn("Net", "no-tag");

            Assert.AreEqual("no-tag", _sink.Events[0].message);
        }

        [Test]
        public void Error_With_Exception_Is_Forwarded()
        {
            var ex = new InvalidOperationException("boom");
            UnityEngine.TestTools.LogAssert.Expect(UnityEngine.LogType.Exception, "InvalidOperationException: boom");
            UnityEngine.TestTools.LogAssert.Expect(UnityEngine.LogType.Error, "[Net] failed");

            global::BurningTimes.Core.Util.Log.Log.Error("Net", "failed", ex);

            Assert.AreEqual(1, _sink.Events.Count);
            Assert.AreSame(ex, _sink.Events[0].exception);
        }

        [Test]
        public void Sink_Exception_Does_Not_Break_Others()
        {
            var bad = new ThrowingSink();
            var good = new RecordingSink();
            global::BurningTimes.Core.Util.Log.Log.AddSink(bad);
            global::BurningTimes.Core.Util.Log.Log.AddSink(good);

            UnityEngine.TestTools.LogAssert.Expect(UnityEngine.LogType.Warning, "[Net] ping");
            global::BurningTimes.Core.Util.Log.Log.Warn("Net", "ping");

            Assert.AreEqual(1, good.Events.Count, "정상 sink는 수신해야 한다");
        }

        private sealed class ThrowingSink : ILogSink
        {
            public void Emit(LogLevel level, string category, string message, Exception exception)
                => throw new InvalidOperationException("sink failure");
        }
    }
}
