using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Grpc.Core;
using Grpc.Net.Client;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.Build;
using UnityEngine;

namespace eagle0.Tests {
    [TestFixture]
    public class ClrRuntimeMigrationGuardrailTests {
        [Test]
        public void StandaloneBuildUsesIl2CppBackend() {
            Assert.AreEqual(
                    ScriptingImplementation.IL2CPP,
                    PlayerSettings.GetScriptingBackend(NamedBuildTarget.Standalone),
                    "Standalone player builds should stay on IL2CPP after the CLR migration.");
        }

        [Test]
        public void StandaloneBuildUsesUnityDotNetApiCompatibility() {
            Assert.AreEqual(
                    ApiCompatibilityLevel.NET_Unity_4_8,
                    PlayerSettings.GetApiCompatibilityLevel(NamedBuildTarget.Standalone),
                    "Standalone player builds should use Unity's .NET Framework profile for the CLR migration.");
        }

        [Test]
        public void IosBuildUsesIl2CppBackend() {
            Assert.AreEqual(
                    ScriptingImplementation.IL2CPP,
                    PlayerSettings.GetScriptingBackend(NamedBuildTarget.iOS),
                    "iOS player builds should stay on IL2CPP after the CLR migration.");
        }

        [Test]
        public void IosBuildUsesUnityDotNetApiCompatibility() {
            Assert.AreEqual(
                    ApiCompatibilityLevel.NET_Unity_4_8,
                    PlayerSettings.GetApiCompatibilityLevel(NamedBuildTarget.iOS),
                    "iOS player builds should use Unity's .NET Framework profile for the CLR migration.");
        }

        [Test]
        public void GrpcClientAssembliesResolveInEditorCompilation() {
            Assert.AreEqual("Grpc.Net.Client", typeof(GrpcChannel).Assembly.GetName().Name);
            Assert.AreEqual("Grpc.Core.Api", typeof(CallInvoker).Assembly.GetName().Name);
        }

        [Test]
        public void PackageRuntimeAssembliesAreImportedForAnyPlatform() {
            var paths = PackageRuntimeAssemblyPaths().ToArray();

            Assert.IsNotEmpty(
                    paths,
                    "Expected NuGet runtime assemblies under Assets/Packages/*/lib/**/*.dll.");

            foreach (var path in paths) {
                var importer = AssetImporter.GetAtPath(path) as PluginImporter;

                Assert.NotNull(importer, $"{path} must be imported as a managed plugin.");
                Assert.IsTrue(
                        importer.GetCompatibleWithAnyPlatform(),
                        $"{path} must stay available to player builds during the CLR migration.");
            }
        }

        [Test]
        public void PackageAnalyzerAssembliesAreNotImportedForPlayerBuilds() {
            var paths = PackageAnalyzerAssemblyPaths().ToArray();

            Assert.IsNotEmpty(
                    paths,
                    "Expected NuGet analyzer assemblies under Assets/Packages/*/analyzers/**/*.dll.");

            foreach (var path in paths) {
                var importer = AssetImporter.GetAtPath(path) as PluginImporter;

                Assert.NotNull(importer, $"{path} must be imported as a managed plugin.");
                Assert.IsFalse(
                        importer.GetCompatibleWithAnyPlatform(),
                        $"{path} is a compiler analyzer/source generator and must not be included in player builds.");
            }
        }

        private static IEnumerable<string> PackageRuntimeAssemblyPaths() {
            return PackageAssemblyPaths("/lib/");
        }

        private static IEnumerable<string> PackageAnalyzerAssemblyPaths() {
            return PackageAssemblyPaths("/analyzers/");
        }

        private static IEnumerable<string> PackageAssemblyPaths(string pathSegment) {
            var projectRoot = Directory.GetParent(Application.dataPath).FullName;
            var packagesPath = Path.Combine(Application.dataPath, "Packages");

            return Directory.EnumerateFiles(packagesPath, "*.dll", SearchOption.AllDirectories)
                    .Where(path => RelativePath(projectRoot, path).Contains(pathSegment))
                    .Select(path => RelativePath(projectRoot, path))
                    .OrderBy(path => path, StringComparer.Ordinal);
        }

        private static string RelativePath(string projectRoot, string path) {
            return path.Substring(projectRoot.Length + 1).Replace(Path.DirectorySeparatorChar, '/');
        }
    }
}
