using Microsoft.VisualStudio.TestTools.UnitTesting;
using NavisworksTransport.Core.AliasTree;
using System;
using System.Data.SQLite;
using System.IO;

namespace NavisworksTransport.UnitTests.Core
{
    [TestClass]
    public class AliasNodeIdentityTests
    {
        // ── 构造 ──

        [TestMethod]
        public void Constructor_ShouldSetBothFields()
        {
            var id = new AliasNodeIdentity("0,2,5", "building/floor/wall#1");
            Assert.AreEqual("0,2,5", id.IndexPath);
            Assert.AreEqual("building/floor/wall#1", id.DisplayPath);
        }

        [TestMethod]
        public void Constructor_WithEmptyIndexPath_IsValid()
        {
            var id = new AliasNodeIdentity("", "building/floor");
            Assert.AreEqual("", id.IndexPath);
            Assert.AreEqual("building/floor", id.DisplayPath);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Constructor_NullDisplayPath_ShouldThrow()
        {
            new AliasNodeIdentity("0,0", null);
        }

        // ── 序列化 ──

        [TestMethod]
        public void ToKey_WithBothFields_ShouldFormatCorrectly()
        {
            var id = new AliasNodeIdentity("0,2,5", "模型/楼层/墙#0");
            Assert.AreEqual("0,2,5||模型/楼层/墙#0", id.ToKey());
        }

        [TestMethod]
        public void ToKey_WithEmptyIndexPath_ShouldReturnDisplayPathOnly()
        {
            var id = new AliasNodeIdentity("", "模型/楼层/墙#0");
            Assert.AreEqual("模型/楼层/墙#0", id.ToKey());
        }

        [TestMethod]
        public void FromKey_NewFormat_ShouldParseCorrectly()
        {
            var id = AliasNodeIdentity.FromKey("0,2,5||模型/楼层/墙#0");
            Assert.AreEqual("0,2,5", id.IndexPath);
            Assert.AreEqual("模型/楼层/墙#0", id.DisplayPath);
        }

        [TestMethod]
        [ExpectedException(typeof(FormatException))]
        public void FromKey_OldFormat_ShouldThrow()
        {
            // 旧格式（无 || 分隔符）已不再支持，按 AGENTS.md "不向后兼容" 原则抛 FormatException
            AliasNodeIdentity.FromKey("building/floor/wall#2");
        }

        [TestMethod]
        [ExpectedException(typeof(FormatException))]
        public void FromKey_OldFormatNoIndex_ShouldThrow()
        {
            AliasNodeIdentity.FromKey("building/floor");
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void FromKey_EmptyString_ShouldThrow()
        {
            AliasNodeIdentity.FromKey("");
        }

        [TestMethod]
        [ExpectedException(typeof(FormatException))]
        public void FromKey_EmptyDisplayAfterSeparator_ShouldThrow()
        {
            AliasNodeIdentity.FromKey("0,0||");
        }

        // ── TryParse ──

        [TestMethod]
        public void TryParseKey_NewFormat_ShouldSucceed()
        {
            Assert.IsTrue(AliasNodeIdentity.TryParseKey("0,0||path#0", out var id));
            Assert.AreEqual("0,0", id.IndexPath);
            Assert.AreEqual("path#0", id.DisplayPath);
        }

        [TestMethod]
        public void TryParseKey_OldFormat_ShouldReturnFalse()
        {
            // 旧格式（无 || 分隔符）已不再支持
            Assert.IsFalse(AliasNodeIdentity.TryParseKey("path#0", out _));
        }

        [TestMethod]
        public void TryParseKey_Empty_ShouldReturnFalse()
        {
            Assert.IsFalse(AliasNodeIdentity.TryParseKey("", out _));
        }

        // ── 查询辅助 ──

        [TestMethod]
        public void GetHierarchicalPath_WithIndex_ShouldStripSuffix()
        {
            var id = new AliasNodeIdentity("0,0", "a/b/c#2");
            Assert.AreEqual("a/b/c", id.GetHierarchicalPath());
        }

        [TestMethod]
        public void GetHierarchicalPath_WithoutIndex_ShouldReturnFull()
        {
            var id = new AliasNodeIdentity("0,0", "a/b/c");
            Assert.AreEqual("a/b/c", id.GetHierarchicalPath());
        }

        [TestMethod]
        public void GetSiblingIndex_WithIndex_ShouldReturn()
        {
            var id = new AliasNodeIdentity("0,0", "a/b#3");
            Assert.AreEqual(3, id.GetSiblingIndex());
        }

        [TestMethod]
        public void GetSiblingIndex_WithoutIndex_ShouldReturnZero()
        {
            var id = new AliasNodeIdentity("0,0", "a/b");
            Assert.AreEqual(0, id.GetSiblingIndex());
        }

        [TestMethod]
        public void HasPathPrefix_Matching_ShouldReturnTrue()
        {
            var id = new AliasNodeIdentity("0,0", "building/floor/wall#0");
            Assert.IsTrue(id.HasPathPrefix("building/floor"));
            Assert.IsTrue(id.HasPathPrefix("building"));
            Assert.IsTrue(id.HasPathPrefix("building/floor/wall"));
        }

        [TestMethod]
        public void HasPathPrefix_NonMatching_ShouldReturnFalse()
        {
            var id = new AliasNodeIdentity("0,0", "building/floor/wall#0");
            Assert.IsFalse(id.HasPathPrefix("building/roof"));
            Assert.IsFalse(id.HasPathPrefix("Build"));
        }

        // ── 值语义 ──

        [TestMethod]
        public void Equals_Identical_ShouldMatch()
        {
            var a = new AliasNodeIdentity("0,1", "path#0");
            var b = new AliasNodeIdentity("0,1", "path#0");
            Assert.AreEqual(a, b);
            Assert.IsTrue(a == b);
            Assert.AreEqual(a.GetHashCode(), b.GetHashCode());
        }

        [TestMethod]
        public void Equals_DifferentIndexPath_ShouldNotMatch()
        {
            var a = new AliasNodeIdentity("0,1", "path");
            var b = new AliasNodeIdentity("0,2", "path");
            Assert.AreNotEqual(a, b);
        }

        [TestMethod]
        public void Equals_DifferentDisplayPath_ShouldNotMatch()
        {
            var a = new AliasNodeIdentity("0,1", "path/a");
            var b = new AliasNodeIdentity("0,1", "path/b");
            Assert.AreNotEqual(a, b);
        }

        [TestMethod]
        public void ToString_ShouldMatchToKey()
        {
            var id = new AliasNodeIdentity("0,1", "path#0");
            Assert.AreEqual(id.ToKey(), id.ToString());
        }
    }

    [TestClass]
    public class AliasDataStoreTests
    {
        private string _dbPath;
        private SQLiteConnection _connection;

        [TestInitialize]
        public void Setup()
        {
            _dbPath = Path.Combine(Path.GetTempPath(), $"AliasDataStoreTest_{Guid.NewGuid():N}.db");
            _connection = new SQLiteConnection($"Data Source={_dbPath};Version=3;");
            _connection.Open();
        }

        [TestCleanup]
        public void Cleanup()
        {
            _connection?.Close();
            _connection?.Dispose();
            try { File.Delete(_dbPath); } catch { }
        }

        private AliasDataStore CreateStore()
        {
            var store = new AliasDataStore(_connection);
            store.EnsureTableExists();
            return store;
        }

        private AliasNodeIdentity Id(string displayPath, string indexPath = "")
            => new AliasNodeIdentity(indexPath, displayPath);

        // ── CRUD ──

        [TestMethod]
        public void SetAndGetAlias_ShouldReturnSavedValue()
        {
            var store = CreateStore();
            store.SetAlias(Id("building/floor", "0,1"), "1F-办公层");
            Assert.AreEqual("1F-办公层", store.GetAlias(Id("building/floor", "0,1")));
        }

        [TestMethod]
        public void GetAlias_Nonexistent_ShouldReturnNull()
        {
            var store = CreateStore();
            Assert.IsNull(store.GetAlias(Id("nonexistent")));
        }

        [TestMethod]
        public void SetAlias_Overwrite_ShouldUpdateValue()
        {
            var store = CreateStore();
            store.SetAlias(Id("wall", "0,0"), "旧名称");
            store.SetAlias(Id("wall", "0,0"), "新名称");
            Assert.AreEqual("新名称", store.GetAlias(Id("wall", "0,0")));
        }

        [TestMethod]
        public void SetAlias_EmptyString_ShouldDelete()
        {
            var store = CreateStore();
            store.SetAlias(Id("x", "0,0"), "原本");
            Assert.AreEqual(1, store.Count);
            store.SetAlias(Id("x", "0,0"), "   ");
            Assert.IsNull(store.GetAlias(Id("x", "0,0")));
            Assert.AreEqual(0, store.Count);
        }

        [TestMethod]
        public void DeleteAlias_ShouldRemoveEntry()
        {
            var store = CreateStore();
            store.SetAlias(Id("x"), "别名");
            Assert.AreEqual(1, store.Count);
            store.DeleteAlias(Id("x"));
            Assert.AreEqual(0, store.Count);
        }

        // ── 批量 ──

        [TestMethod]
        public void DeleteByPathPrefix_ShouldRemoveAllUnderPath()
        {
            var store = CreateStore();
            store.SetAlias(Id("building/floor/1"), "别名1");
            store.SetAlias(Id("building/floor/2"), "别名2");
            store.SetAlias(Id("building/roof/3"), "别名3");

            store.DeleteAliasesByPathPrefix("building/floor");

            Assert.IsNull(store.GetAlias(Id("building/floor/1")));
            Assert.IsNull(store.GetAlias(Id("building/floor/2")));
            Assert.IsNotNull(store.GetAlias(Id("building/roof/3")));
        }

        [TestMethod]
        public void LoadAll_ShouldReturnAll()
        {
            var store = CreateStore();
            store.SetAlias(Id("a", "0,0"), "别名A");
            store.SetAlias(Id("b", "1,0"), "别名B");

            var all = store.LoadAll();
            Assert.AreEqual(2, all.Count);
            Assert.AreEqual("别名A", all[Id("a", "0,0").ToKey()]);
            Assert.AreEqual("别名B", all[Id("b", "1,0").ToKey()]);
        }

        [TestMethod]
        public void Count_ShouldReflectInserts()
        {
            var store = CreateStore();
            Assert.AreEqual(0, store.Count);
            store.SetAlias(Id("a"), "alias1");
            store.SetAlias(Id("b"), "alias2");
            Assert.AreEqual(2, store.Count);
        }

        // ── ImportBatch ──

        [TestMethod]
        public void ImportBatch_ShouldMergeAndOverwrite()
        {
            var store = CreateStore();
            store.SetAlias(Id("existing", "0,0"), "保留值");

            var entries = new System.Collections.Generic.Dictionary<string, string>
            {
                { Id("existing", "0,0").ToKey(), "覆盖值" },
                { Id("new", "1,0").ToKey(), "新值" }
            };
            store.ImportBatch(entries);

            Assert.AreEqual("覆盖值", store.GetAlias(Id("existing", "0,0")));
            Assert.AreEqual("新值", store.GetAlias(Id("new", "1,0")));
            Assert.AreEqual(2, store.Count);
        }

        [TestMethod]
        public void ClearAll_ShouldRemoveEverything()
        {
            var store = CreateStore();
            store.SetAlias(Id("a"), "别名1");
            store.SetAlias(Id("b"), "别名2");
            store.ClearAll();
            Assert.AreEqual(0, store.Count);
        }

        // ── 旧格式已不支持（AGENTS.md "不向后兼容" 原则）──

        [TestMethod]
        [ExpectedException(typeof(FormatException))]
        public void OldFormatKey_ShouldThrow()
        {
            var store = CreateStore();
            string oldKey = "building/floor/wall#0";
            // 旧格式 key 无 || 分隔符，FromKey 应抛 FormatException
            store.SetAlias(AliasNodeIdentity.FromKey(oldKey), "别名");
        }
    }
}
