using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;

public static class URPMigrationAuditTool {
    private const string ReportDirectory = "Temp/urp_migration";
    private const string MarkdownReportName = "urp_migration_inventory.md";
    private const string MaterialCsvName = "urp_migration_materials.csv";
    private const string MaterialReferenceCsvName = "urp_migration_material_references.csv";
    private const string ShaderFeatureCsvName = "urp_migration_shader_features.csv";

    private static readonly string[] BuiltInOnlyPatterns = {
        "GrabPass",
        "#pragma surface",
        "UnityCG.cginc",
        "UnityUI.cginc",
        "UNITY_MATRIX_",
        "sampler2D _GrabTexture",
        "tessell",
        "Tessellation",
    };

    private static readonly string[] ShaderLikeExtensions = {
        ".shader",
        ".cginc",
        ".hlsl",
        ".shadergraph",
    };

    private static readonly string[] ReferenceScanExtensions = {
        ".unity",
        ".prefab",
        ".asset",
        ".mat",
        ".shader",
        ".cginc",
        ".hlsl",
        ".shadergraph",
    };

    private static readonly string[] PostProcessingPatterns = {
        "UnityEngine.Rendering.PostProcessing",
        "PostProcessProfile",
        "PostProcessLayer",
        "PostProcessVolume",
        "com.unity.postprocessing",
    };

    private static readonly string[] ProductionReferenceRoots = {
        "Assets/Scenes/",
        "Assets/Eagle/",
        "Assets/Shardok/",
        "Assets/ConnectionHandler/",
        "Assets/UI/",
        "Assets/common/",
        "Assets/Tutorial/",
        "Assets/Resources/",
        "Assets/Materials/",
    };

    [MenuItem("Eagle0/Rendering/Generate URP Migration Inventory")]
    public static void GenerateInventory() {
        var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
        var outputDirectory = Path.Combine(projectRoot, ReportDirectory);
        Directory.CreateDirectory(outputDirectory);

        var shaders = LoadAssets<Shader>("t:Shader")
                              .Select(shader => new ShaderRecord {
                                  Path = AssetDatabase.GetAssetPath(shader),
                                  Name = shader.name
                              })
                              .OrderBy(shader => shader.Path, StringComparer.Ordinal)
                              .ToList();

        var materials = LoadAssets<Material>("t:Material")
                                .Select(material => new MaterialRecord {
                                    Path = AssetDatabase.GetAssetPath(material),
                                    ShaderName = material.shader != null ? material.shader.name
                                                                         : "<missing shader>"
                                })
                                .OrderBy(material => material.Path, StringComparer.Ordinal)
                                .ToList();

        var shaderFeatureHits =
                ScanTextPatterns(projectRoot, ShaderLikeExtensions, BuiltInOnlyPatterns);
        var postProcessingHits =
                ScanTextPatterns(projectRoot, ReferenceScanExtensions, PostProcessingPatterns);
        var materialReferences = ScanProductionMaterialReferences(projectRoot, materials);

        var markdownPath = Path.Combine(outputDirectory, MarkdownReportName);
        var materialCsvPath = Path.Combine(outputDirectory, MaterialCsvName);
        var materialReferenceCsvPath = Path.Combine(outputDirectory, MaterialReferenceCsvName);
        var shaderFeatureCsvPath = Path.Combine(outputDirectory, ShaderFeatureCsvName);

        File.WriteAllText(
                markdownPath,
                BuildMarkdownReport(
                        shaders,
                        materials,
                        materialReferences,
                        shaderFeatureHits,
                        postProcessingHits));
        File.WriteAllLines(materialCsvPath, BuildMaterialCsv(materials));
        File.WriteAllLines(materialReferenceCsvPath, BuildMaterialReferenceCsv(materialReferences));
        File.WriteAllLines(shaderFeatureCsvPath, BuildShaderFeatureCsv(shaderFeatureHits));

        Debug.Log(
                $"URP migration inventory written to {markdownPath}\n" +
                $"Material CSV written to {materialCsvPath}\n" +
                $"Material reference CSV written to {materialReferenceCsvPath}\n" +
                $"Shader feature CSV written to {shaderFeatureCsvPath}");
    }

    private static IEnumerable<T> LoadAssets<T>(string filter)
            where T : UnityEngine.Object {
        foreach (var guid in AssetDatabase.FindAssets(filter)) {
            var path = AssetDatabase.GUIDToAssetPath(guid);
            var asset = AssetDatabase.LoadAssetAtPath<T>(path);
            if (asset != null) { yield return asset; }
        }
    }

    private static List<TextPatternHit> ScanTextPatterns(
            string projectRoot,
            IReadOnlyCollection<string> extensions,
            IReadOnlyCollection<string> patterns) {
        var results = new List<TextPatternHit>();
        foreach (var assetPath in AssetDatabase.GetAllAssetPaths()) {
            if (!assetPath.StartsWith("Assets/", StringComparison.Ordinal)) { continue; }

            var extension = Path.GetExtension(assetPath);
            if (!extensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) { continue; }

            var fullPath = Path.Combine(projectRoot, assetPath);
            if (!File.Exists(fullPath)) { continue; }

            string[] lines;
            try {
                lines = File.ReadAllLines(fullPath);
            } catch (Exception e) {
                Debug.LogWarning($"Skipping {assetPath}: {e.Message}");
                continue;
            }

            for (var i = 0; i < lines.Length; i++) {
                foreach (var pattern in patterns) {
                    if (lines[i].IndexOf(pattern, StringComparison.OrdinalIgnoreCase) < 0) {
                        continue;
                    }

                    results.Add(new TextPatternHit {
                        Path = assetPath,
                        LineNumber = i + 1,
                        Pattern = pattern,
                        Snippet = lines[i].Trim()
                    });
                }
            }
        }

        return results.OrderBy(hit => hit.Path, StringComparer.Ordinal)
                .ThenBy(hit => hit.LineNumber)
                .ThenBy(hit => hit.Pattern, StringComparer.Ordinal)
                .ToList();
    }

    private static List<MaterialReferenceRecord> ScanProductionMaterialReferences(
            string projectRoot,
            IReadOnlyCollection<MaterialRecord> materials) {
        var materialByGuid = materials
                                     .Select(material => new {
                                         Guid = AssetDatabase.AssetPathToGUID(material.Path),
                                         Material = material
                                     })
                                     .Where(record => !string.IsNullOrEmpty(record.Guid))
                                     .ToDictionary(
                                             record => record.Guid,
                                             record => record.Material,
                                             StringComparer.OrdinalIgnoreCase);

        var results = new List<MaterialReferenceRecord>();
        foreach (var assetPath in AssetDatabase.GetAllAssetPaths()) {
            if (!IsProductionReferencePath(assetPath)) { continue; }

            var extension = Path.GetExtension(assetPath);
            if (!ReferenceScanExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) {
                continue;
            }

            var fullPath = Path.Combine(projectRoot, assetPath);
            if (!File.Exists(fullPath)) { continue; }

            string text;
            try {
                text = File.ReadAllText(fullPath);
            } catch (Exception e) {
                Debug.LogWarning($"Skipping {assetPath}: {e.Message}");
                continue;
            }

            foreach (var entry in materialByGuid) {
                if (text.IndexOf(entry.Key, StringComparison.OrdinalIgnoreCase) < 0) { continue; }

                results.Add(new MaterialReferenceRecord {
                    MaterialPath = entry.Value.Path,
                    ShaderName = entry.Value.ShaderName,
                    ReferencePath = assetPath
                });
            }
        }

        return results.OrderBy(record => record.MaterialPath, StringComparer.Ordinal)
                .ThenBy(record => record.ReferencePath, StringComparer.Ordinal)
                .ToList();
    }

    private static bool IsProductionReferencePath(string assetPath) {
        return ProductionReferenceRoots.Any(
                root => assetPath.StartsWith(root, StringComparison.Ordinal));
    }

    private static string BuildMarkdownReport(
            IReadOnlyList<ShaderRecord> shaders,
            IReadOnlyList<MaterialRecord> materials,
            IReadOnlyList<MaterialReferenceRecord> materialReferences,
            IReadOnlyList<TextPatternHit> shaderFeatureHits,
            IReadOnlyList<TextPatternHit> postProcessingHits) {
        var report = new StringBuilder();
        report.AppendLine("# URP Migration Inventory");
        report.AppendLine();
        report.AppendLine($"Generated: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC");
        report.AppendLine();

        report.AppendLine("## Render Pipeline Settings");
        report.AppendLine();
        AppendRenderPipelineSetting(
                report,
                "Graphics default",
                GraphicsSettings.defaultRenderPipeline);
        AppendRenderPipelineSetting(
                report,
                "Quality active",
                QualitySettings.GetRenderPipelineAssetAt(QualitySettings.GetQualityLevel()));
        report.AppendLine();

        report.AppendLine("## Summary");
        report.AppendLine();
        report.AppendLine($"- Shader assets: {shaders.Count}");
        report.AppendLine($"- Material assets: {materials.Count}");
        report.AppendLine(
                $"- Distinct material shaders: {materials.Select(m => m.ShaderName).Distinct().Count()}");
        report.AppendLine($"- Production material references: {materialReferences.Count}");
        report.AppendLine($"- Built-in-only shader feature hits: {shaderFeatureHits.Count}");
        report.AppendLine($"- Post Processing Stack reference hits: {postProcessingHits.Count}");
        report.AppendLine();

        report.AppendLine("## Material Shader Usage");
        report.AppendLine();
        report.AppendLine("| Shader | Material Count |");
        report.AppendLine("|---|---:|");
        foreach (var group in materials.GroupBy(material => material.ShaderName)
                         .OrderByDescending(group => group.Count())
                         .ThenBy(group => group.Key, StringComparer.Ordinal)) {
            report.AppendLine($"| {Markdown(group.Key)} | {group.Count()} |");
        }
        report.AppendLine();

        report.AppendLine("## Production Material Reference Shader Usage");
        report.AppendLine();
        report.AppendLine("| Shader | Referenced Material Count | Reference Count |");
        report.AppendLine("|---|---:|---:|");
        foreach (var group in materialReferences.GroupBy(reference => reference.ShaderName)
                         .OrderByDescending(group => group.Count())
                         .ThenBy(group => group.Key, StringComparer.Ordinal)) {
            report.AppendLine(
                    $"| {Markdown(group.Key)} | {group.Select(reference => reference.MaterialPath).Distinct().Count()} | {group.Count()} |");
        }
        report.AppendLine();

        report.AppendLine("## Built-in-only Shader Feature Hits");
        report.AppendLine();
        AppendPatternSummary(report, shaderFeatureHits);
        report.AppendLine();

        report.AppendLine("## Post Processing Stack References");
        report.AppendLine();
        AppendPatternSummary(report, postProcessingHits);
        report.AppendLine();

        report.AppendLine("## Shader Assets");
        report.AppendLine();
        report.AppendLine("| Shader | Path |");
        report.AppendLine("|---|---|");
        foreach (var shader in shaders) {
            report.AppendLine($"| {Markdown(shader.Name)} | `{shader.Path}` |");
        }
        report.AppendLine();

        report.AppendLine("## Build Scenes");
        report.AppendLine();
        foreach (var scene in EditorBuildSettings.scenes) {
            var marker = scene.enabled ? "enabled" : "disabled";
            report.AppendLine($"- `{scene.path}` ({marker})");
        }

        return report.ToString();
    }

    private static void
    AppendRenderPipelineSetting(StringBuilder report, string label, RenderPipelineAsset asset) {
        if (asset == null) {
            report.AppendLine($"- {label}: Built-in Render Pipeline");
            return;
        }

        report.AppendLine($"- {label}: `{AssetDatabase.GetAssetPath(asset)}`");
    }

    private static void AppendPatternSummary(
            StringBuilder report,
            IReadOnlyList<TextPatternHit> hits) {
        if (hits.Count == 0) {
            report.AppendLine("No hits found.");
            return;
        }

        report.AppendLine("| Pattern | Hit Count |");
        report.AppendLine("|---|---:|");
        foreach (var group in hits.GroupBy(hit => hit.Pattern)
                         .OrderByDescending(group => group.Count())
                         .ThenBy(group => group.Key, StringComparer.Ordinal)) {
            report.AppendLine($"| `{Markdown(group.Key)}` | {group.Count()} |");
        }
        report.AppendLine();
        report.AppendLine("| Path | Line | Pattern | Snippet |");
        report.AppendLine("|---|---:|---|---|");
        foreach (var hit in hits.Take(200)) {
            report.AppendLine(
                    $"| `{hit.Path}` | {hit.LineNumber} | `{Markdown(hit.Pattern)}` | {Markdown(hit.Snippet)} |");
        }
        if (hits.Count > 200) {
            report.AppendLine($"| ... | ... | ... | {hits.Count - 200} more hits omitted |");
        }
    }

    private static IEnumerable<string> BuildMaterialCsv(IEnumerable<MaterialRecord> materials) {
        yield return "Material Path,Shader";
        foreach (var material in materials) {
            yield return $"{Csv(material.Path)},{Csv(material.ShaderName)}";
        }
    }

    private static IEnumerable<string> BuildMaterialReferenceCsv(
            IEnumerable<MaterialReferenceRecord> references) {
        yield return "Material Path,Shader,Reference Path";
        foreach (var reference in references) {
            yield return $"{Csv(reference.MaterialPath)},{Csv(reference.ShaderName)},{Csv(reference.ReferencePath)}";
        }
    }

    private static IEnumerable<string> BuildShaderFeatureCsv(IEnumerable<TextPatternHit> hits) {
        yield return "Path,Line,Pattern,Snippet";
        foreach (var hit in hits) {
            yield return $"{Csv(hit.Path)},{hit.LineNumber},{Csv(hit.Pattern)},{Csv(hit.Snippet)}";
        }
    }

    private static string Csv(string value) {
        if (value.IndexOfAny(new[] { ',', '"', '\n', '\r' }) < 0) { return value; }
        return "\"" + value.Replace("\"", "\"\"") + "\"";
    }

    private static string Markdown(string value) {
        return value.Replace("|", "\\|").Replace("\n", " ").Replace("\r", " ");
    }

    private class ShaderRecord {
        public string Path;
        public string Name;
    }

    private class MaterialRecord {
        public string Path;
        public string ShaderName;
    }

    private class MaterialReferenceRecord {
        public string MaterialPath;
        public string ShaderName;
        public string ReferencePath;
    }

    private class TextPatternHit {
        public string Path;
        public int LineNumber;
        public string Pattern;
        public string Snippet;
    }
}
