using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NUnit.Framework;
using UnityEngine;

namespace eagle0.Tests {
    [TestFixture]
    public class RuntimeUnityEditorReferenceTests {
        [Test]
        public void RuntimeScriptsDoNotReferenceUnityEditorOutsideEditorGuards() {
            var projectRoot = Directory.GetParent(Application.dataPath).FullName;
            var violations = new List<string>();

            foreach (var path in RuntimeScripts(projectRoot)) {
                var lines = File.ReadAllLines(path);
                var preprocessorStack = new List<bool>();

                for (var i = 0; i < lines.Length; i++) {
                    var trimmed = lines[i].Trim();
                    UpdateEditorGuardState(preprocessorStack, trimmed);

                    if (ReferencesUnityEditor(trimmed) && !IsInsideEditorGuard(preprocessorStack)) {
                        violations.Add($"{RelativePath(projectRoot, path)}:{i + 1}");
                    }
                }
            }

            Assert.IsEmpty(
                    violations,
                    "Runtime scripts must keep UnityEditor references inside #if UNITY_EDITOR guards:\n" +
                            string.Join("\n", violations));
        }

        private static void UpdateEditorGuardState(List<bool> preprocessorStack, string trimmed) {
            if (trimmed == "#if UNITY_EDITOR") {
                preprocessorStack.Add(true);
            } else if (
                    trimmed.StartsWith("#if ", StringComparison.Ordinal) ||
                    trimmed.StartsWith("#if\t", StringComparison.Ordinal)) {
                preprocessorStack.Add(false);
            } else if (
                    trimmed.StartsWith("#elif", StringComparison.Ordinal) ||
                    trimmed.StartsWith("#else", StringComparison.Ordinal)) {
                if (preprocessorStack.Count > 0) {
                    preprocessorStack[preprocessorStack.Count - 1] = false;
                }
            } else if (trimmed.StartsWith("#endif", StringComparison.Ordinal)) {
                if (preprocessorStack.Count > 0) {
                    preprocessorStack.RemoveAt(preprocessorStack.Count - 1);
                }
            }
        }

        private static bool IsInsideEditorGuard(List<bool> preprocessorStack) {
            return preprocessorStack.Any(isEditorOnly => isEditorOnly);
        }

        private static bool ReferencesUnityEditor(string trimmed) {
            return !trimmed.StartsWith("//", StringComparison.Ordinal) &&
                   (trimmed.StartsWith("using UnityEditor;", StringComparison.Ordinal) ||
                    trimmed.StartsWith("using UnityEditor.", StringComparison.Ordinal) ||
                    trimmed.Contains("UnityEditor.", StringComparison.Ordinal));
        }

        private static IEnumerable<string> RuntimeScripts(string projectRoot) {
            return Directory
                    .EnumerateFiles(
                            Path.Combine(projectRoot, "Assets"),
                            "*.cs",
                            SearchOption.AllDirectories)
                    .Where(path => !IsEditorPath(projectRoot, path))
                    .OrderBy(path => path, StringComparer.Ordinal);
        }

        private static bool IsEditorPath(string projectRoot, string path) {
            return RelativePath(projectRoot, path).Split('/').Any(part => part == "Editor");
        }

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