using System;
using NUnit.Framework;
using UnityEditor;
using MCPForUnity.Editor.Services;
using MCPForUnity.Editor.Constants;

namespace MCPForUnityTests.Editor.Services
{
    public class PackageUpdateServiceTests
    {
        private PackageUpdateService _service;
        private const string TestLastCheckDateKey = EditorPrefKeys.LastUpdateCheck;
        private const string TestCachedVersionKey = EditorPrefKeys.LatestKnownVersion;
        private const string TestAssetStoreLastCheckDateKey = EditorPrefKeys.LastAssetStoreUpdateCheck;
        private const string TestAssetStoreCachedVersionKey = EditorPrefKeys.LatestKnownAssetStoreVersion;

        [SetUp]
        public void SetUp()
        {
            _service = new PackageUpdateService();

            // Clean up any existing test data
            CleanupEditorPrefs();
        }

        [TearDown]
        public void TearDown()
        {
            // Clean up test data
            CleanupEditorPrefs();
        }

        private void CleanupEditorPrefs()
        {
            if (EditorPrefs.HasKey(TestLastCheckDateKey))
            {
                EditorPrefs.DeleteKey(TestLastCheckDateKey);
            }
            if (EditorPrefs.HasKey(TestCachedVersionKey))
            {
                EditorPrefs.DeleteKey(TestCachedVersionKey);
            }
            if (EditorPrefs.HasKey(TestAssetStoreLastCheckDateKey))
            {
                EditorPrefs.DeleteKey(TestAssetStoreLastCheckDateKey);
            }
            if (EditorPrefs.HasKey(TestAssetStoreCachedVersionKey))
            {
                EditorPrefs.DeleteKey(TestAssetStoreCachedVersionKey);
            }
        }

        [Test]
        public void IsNewerVersion_ReturnsTrue_WhenMajorVersionIsNewer()
        {
            bool result = _service.IsNewerVersion("2.0.0", "1.0.0");
            Assert.IsTrue(result, "2.0.0 should be newer than 1.0.0");
        }

        [Test]
        public void IsNewerVersion_ReturnsTrue_WhenMinorVersionIsNewer()
        {
            bool result = _service.IsNewerVersion("1.2.0", "1.1.0");
            Assert.IsTrue(result, "1.2.0 should be newer than 1.1.0");
        }

        [Test]
        public void IsNewerVersion_ReturnsTrue_WhenPatchVersionIsNewer()
        {
            bool result = _service.IsNewerVersion("1.0.2", "1.0.1");
            Assert.IsTrue(result, "1.0.2 should be newer than 1.0.1");
        }

        [Test]
        public void IsNewerVersion_ReturnsFalse_WhenVersionsAreEqual()
        {
            bool result = _service.IsNewerVersion("1.0.0", "1.0.0");
            Assert.IsFalse(result, "Same versions should return false");
        }

        [Test]
        public void IsNewerVersion_ReturnsFalse_WhenVersionIsOlder()
        {
            bool result = _service.IsNewerVersion("1.0.0", "2.0.0");
            Assert.IsFalse(result, "1.0.0 should not be newer than 2.0.0");
        }

        [Test]
        public void IsNewerVersion_HandlesVersionPrefix_v()
        {
            bool result = _service.IsNewerVersion("v2.0.0", "v1.0.0");
            Assert.IsTrue(result, "Should handle 'v' prefix correctly");
        }

        [Test]
        public void IsNewerVersion_HandlesVersionPrefix_V()
        {
            bool result = _service.IsNewerVersion("V2.0.0", "V1.0.0");
            Assert.IsTrue(result, "Should handle 'V' prefix correctly");
        }

        [Test]
        public void IsNewerVersion_HandlesMixedPrefixes()
        {
            bool result = _service.IsNewerVersion("v2.0.0", "1.0.0");
            Assert.IsTrue(result, "Should handle mixed prefixes correctly");
        }

        [Test]
        public void IsNewerVersion_ComparesCorrectly_WhenMajorDiffers()
        {
            bool result1 = _service.IsNewerVersion("10.0.0", "9.0.0");
            bool result2 = _service.IsNewerVersion("2.0.0", "10.0.0");

            Assert.IsTrue(result1, "10.0.0 should be newer than 9.0.0");
            Assert.IsFalse(result2, "2.0.0 should not be newer than 10.0.0");
        }

        [Test]
        public void IsNewerVersion_ReturnsFalse_OnInvalidVersionFormat()
        {
            // Service should handle errors gracefully
            bool result = _service.IsNewerVersion("invalid", "1.0.0");
            Assert.IsFalse(result, "Should return false for invalid version format");
        }

        [Test]
        public void CheckForUpdate_ReturnsCachedVersion_WhenCacheIsValid()
        {
            // Arrange: Set up valid cache
            string today = DateTime.Now.ToString("yyyy-MM-dd");
            string cachedVersion = "5.5.5";
            EditorPrefs.SetString(TestLastCheckDateKey, today);
            EditorPrefs.SetString(TestCachedVersionKey, cachedVersion);

            // Act
            var result = _service.CheckForUpdate("5.0.0");

            // Assert
            Assert.IsTrue(result.CheckSucceeded, "Check should succeed with valid cache");
            Assert.AreEqual(cachedVersion, result.LatestVersion, "Should return cached version");
            Assert.IsTrue(result.UpdateAvailable, "Update should be available (5.5.5 > 5.0.0)");
        }

        [Test]
        public void CheckForUpdate_DetectsUpdateAvailable_WhenNewerVersionCached()
        {
            // Arrange
            string today = DateTime.Now.ToString("yyyy-MM-dd");
            EditorPrefs.SetString(TestLastCheckDateKey, today);
            EditorPrefs.SetString(TestCachedVersionKey, "6.0.0");

            // Act
            var result = _service.CheckForUpdate("5.0.0");

            // Assert
            Assert.IsTrue(result.UpdateAvailable, "Should detect update is available");
            Assert.AreEqual("6.0.0", result.LatestVersion);
        }

        [Test]
        public void CheckForUpdate_DetectsNoUpdate_WhenVersionsMatch()
        {
            // Arrange
            string today = DateTime.Now.ToString("yyyy-MM-dd");
            EditorPrefs.SetString(TestLastCheckDateKey, today);
            EditorPrefs.SetString(TestCachedVersionKey, "5.0.0");

            // Act
            var result = _service.CheckForUpdate("5.0.0");

            // Assert
            Assert.IsFalse(result.UpdateAvailable, "Should detect no update needed");
            Assert.AreEqual("5.0.0", result.LatestVersion);
        }

        [Test]
        public void CheckForUpdate_DetectsNoUpdate_WhenCurrentVersionIsNewer()
        {
            // Arrange
            string today = DateTime.Now.ToString("yyyy-MM-dd");
            EditorPrefs.SetString(TestLastCheckDateKey, today);
            EditorPrefs.SetString(TestCachedVersionKey, "5.0.0");

            // Act
            var result = _service.CheckForUpdate("6.0.0");

            // Assert
            Assert.IsFalse(result.UpdateAvailable, "Should detect no update when current is newer");
            Assert.AreEqual("5.0.0", result.LatestVersion);
        }

        [Test]
        public void CheckForUpdate_IgnoresExpiredCache_AndAttemptsFreshFetch()
        {
            // Arrange: Set cache from yesterday (expired)
            string yesterday = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd");
            string cachedVersion = "4.0.0";
            EditorPrefs.SetString(TestLastCheckDateKey, yesterday);
            EditorPrefs.SetString(TestCachedVersionKey, cachedVersion);

            // Act
            var result = _service.CheckForUpdate("5.0.0");

            // Assert
            Assert.IsNotNull(result, "Should return a result");
            
            // If the check succeeded (network available), verify it didn't use the expired cache
            if (result.CheckSucceeded)
            {
                Assert.AreNotEqual(cachedVersion, result.LatestVersion, 
                    "Should not return expired cached version when fresh fetch succeeds");
                Assert.IsNotNull(result.LatestVersion, "Should have fetched a new version");
            }
            else
            {
                // If offline, check should fail (not succeed with cached data)
                Assert.IsFalse(result.UpdateAvailable, 
                    "Should not report update available when fetch fails and cache is expired");
            }
        }

        [Test]
        public void CheckForUpdate_UsesAssetStoreCache_WhenCacheIsValid()
        {
            // Arrange: Set up valid Asset Store cache
            string today = DateTime.Now.ToString("yyyy-MM-dd");
            string cachedVersion = "9.0.1";
            EditorPrefs.SetString(TestAssetStoreLastCheckDateKey, today);
            EditorPrefs.SetString(TestAssetStoreCachedVersionKey, cachedVersion);

            var mockService = new TestablePackageUpdateService
            {
                IsGitInstallationResult = false,
                AssetStoreFetchResult = "9.9.9"
            };

            // Act
            var result = mockService.CheckForUpdate("9.0.0");

            // Assert
            Assert.IsTrue(result.CheckSucceeded, "Check should succeed with valid Asset Store cache");
            Assert.AreEqual(cachedVersion, result.LatestVersion, "Should return cached Asset Store version");
            Assert.IsTrue(result.UpdateAvailable, "Update should be available (9.0.1 > 9.0.0)");
            Assert.IsFalse(mockService.AssetStoreFetchCalled, "Should not fetch when Asset Store cache is valid");
        }

        [Test]
        public void CheckForUpdate_FetchesAssetStoreJson_WhenCacheExpired()
        {
            // Arrange: Set expired Asset Store cache and a valid Git cache to ensure separation
            string yesterday = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd");
            EditorPrefs.SetString(TestAssetStoreLastCheckDateKey, yesterday);
            EditorPrefs.SetString(TestAssetStoreCachedVersionKey, "9.0.0");
            EditorPrefs.SetString(TestLastCheckDateKey, DateTime.Now.ToString("yyyy-MM-dd"));
            EditorPrefs.SetString(TestCachedVersionKey, "99.0.0");

            var mockService = new TestablePackageUpdateService
            {
                IsGitInstallationResult = false,
                AssetStoreFetchResult = "9.1.0"
            };

            // Act
            var result = mockService.CheckForUpdate("9.0.0");

            // Assert
            Assert.IsTrue(result.CheckSucceeded, "Check should succeed when fetch returns a version");
            Assert.AreEqual("9.1.0", result.LatestVersion, "Should use fetched Asset Store version");
            Assert.IsTrue(mockService.AssetStoreFetchCalled, "Should fetch when Asset Store cache is expired");
        }

        [Test]
        public void CheckForUpdate_ReturnsAssetStoreFailureMessage_WhenFetchFails()
        {
            // Arrange
            var mockService = new TestablePackageUpdateService
            {
                IsGitInstallationResult = false,
                AssetStoreFetchResult = null
            };

            // Act
            var result = mockService.CheckForUpdate("9.0.0");

            // Assert
            Assert.IsFalse(result.CheckSucceeded, "Check should fail when Asset Store fetch fails");
            Assert.IsFalse(result.UpdateAvailable, "No update should be reported when fetch fails");
            Assert.AreEqual("Failed to check for Asset Store updates (network issue or offline)", result.Message);
            Assert.IsNull(result.LatestVersion, "Latest version should be null when fetch fails");
        }

        [Test]
        public void ClearCache_RemovesAllCachedData()
        {
            // Arrange: Set up cache
            EditorPrefs.SetString(TestLastCheckDateKey, DateTime.Now.ToString("yyyy-MM-dd"));
            EditorPrefs.SetString(TestCachedVersionKey, "5.0.0");
            EditorPrefs.SetString(TestAssetStoreLastCheckDateKey, DateTime.Now.ToString("yyyy-MM-dd"));
            EditorPrefs.SetString(TestAssetStoreCachedVersionKey, "9.0.0");

            // Verify cache exists
            Assert.IsTrue(EditorPrefs.HasKey(TestLastCheckDateKey), "Cache should exist before clearing");
            Assert.IsTrue(EditorPrefs.HasKey(TestCachedVersionKey), "Cache should exist before clearing");
            Assert.IsTrue(EditorPrefs.HasKey(TestAssetStoreLastCheckDateKey), "Asset Store cache should exist before clearing");
            Assert.IsTrue(EditorPrefs.HasKey(TestAssetStoreCachedVersionKey), "Asset Store cache should exist before clearing");

            // Act
            _service.ClearCache();

            // Assert
            Assert.IsFalse(EditorPrefs.HasKey(TestLastCheckDateKey), "Date cache should be cleared");
            Assert.IsFalse(EditorPrefs.HasKey(TestCachedVersionKey), "Version cache should be cleared");
            Assert.IsFalse(EditorPrefs.HasKey(TestAssetStoreLastCheckDateKey), "Asset Store date cache should be cleared");
            Assert.IsFalse(EditorPrefs.HasKey(TestAssetStoreCachedVersionKey), "Asset Store version cache should be cleared");
        }

        [Test]
        public void ClearCache_DoesNotThrow_WhenNoCacheExists()
        {
            // Ensure no cache exists
            CleanupEditorPrefs();

            // Act & Assert - should not throw
            Assert.DoesNotThrow(() => _service.ClearCache(), "Should not throw when clearing non-existent cache");
        }
    }

    /// <summary>
    /// Testable implementation that allows forcing install type and fetch results.
    /// </summary>
    internal class TestablePackageUpdateService : PackageUpdateService
    {
        public bool IsGitInstallationResult { get; set; } = true;
        public string GitFetchResult { get; set; }
        public string AssetStoreFetchResult { get; set; }
        public bool GitFetchCalled { get; private set; }
        public bool AssetStoreFetchCalled { get; private set; }

        public override bool IsGitInstallation()
        {
            return IsGitInstallationResult;
        }

        protected override string FetchLatestVersionFromGitHub(string branch)
        {
            GitFetchCalled = true;
            return GitFetchResult;
        }

        protected override string FetchLatestVersionFromAssetStoreJson()
        {
            AssetStoreFetchCalled = true;
            return AssetStoreFetchResult;
        }
    }
}
