using System;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine;
using FrameDoctor.Editor.Config;

namespace FrameDoctor.Editor.Logic
{
    // Exports captured profiler data to CSV format for AI analysis.
    public class DataExporter
    {
        // Export captured profiler data to CSV files.
        // data: The captured profiler data
        // returns: Path to the export directory, or null if export failed
        public string Export(CaptureData data)
        {
            if (data.Frames.Count == 0)
            {
                Debug.LogError("[FrameDoctor] No frames captured. Make sure to play your game while capturing.");
                return null;
            }

            try
            {
                var exportPath = Config.FrameDoctorSettings.Instance.ExportPath;
                if (string.IsNullOrEmpty(exportPath))
                {
                    exportPath = Path.Combine(Application.persistentDataPath, "FrameDoctor");
                }

                var sessionName = GenerateSessionName();
                var sessionPath = Path.Combine(exportPath, sessionName);
                Directory.CreateDirectory(sessionPath);

                ExportFrameData(sessionPath, data);
                ExportGcData(sessionPath, data);
                ExportRenderingData(sessionPath, data);
                ExportAssetInventory(sessionPath, data);
                ExportSummary(sessionPath, data);

                FrameDoctorSettings.Log($"[FrameDoctor] Exported {data.Frames.Count} frames to: {sessionPath}");
                return sessionPath;
            }
            catch (Exception e)
            {
                Debug.LogError($"[FrameDoctor] Export failed: {e.Message}");
                return null;
            }
        }

        private void ExportFrameData(string sessionPath, CaptureData data)
        {
            var filePath = Path.Combine(sessionPath, "frame_timing.csv");
            var sb = new StringBuilder();

            // Header with all timing metrics
            sb.AppendLine("frameIndex,startTimeMs,durationMs,cpuFrameTimeMs,cpuMainThreadMs,cpuRenderThreadMs,gpuFrameTimeMs,scriptsMs,physicsMs,animationMs,renderingMs,uiMs,frameDoctorOverheadMs");

            foreach (var frame in data.Frames)
            {
                sb.AppendLine(string.Join(",",
                    frame.FrameIndex,
                    frame.StartTimeMs.ToString("F3"),
                    frame.DurationMs.ToString("F3"),
                    frame.CpuFrameTimeMs.ToString("F3"),
                    frame.CpuMainThreadMs.ToString("F3"),
                    frame.CpuRenderThreadMs.ToString("F3"),
                    frame.GpuFrameTimeMs.ToString("F3"),
                    frame.ScriptsMs.ToString("F3"),
                    frame.PhysicsMs.ToString("F3"),
                    frame.AnimationMs.ToString("F3"),
                    frame.RenderingMs.ToString("F3"),
                    frame.UIMs.ToString("F3"),
                    frame.FrameDoctorOverheadMs.ToString("F4")
                ));
            }

            File.WriteAllText(filePath, sb.ToString());
        }

        private void ExportGcData(string sessionPath, CaptureData data)
        {
            var filePath = Path.Combine(sessionPath, "memory.csv");
            var sb = new StringBuilder();

            // Header with memory metrics
            sb.AppendLine("frameIndex,totalUsedMemoryBytes,gcUsedMemoryBytes,gcAllocatedInFrameBytes,gcAllocCountInFrame");

            foreach (var frame in data.Frames)
            {
                sb.AppendLine(string.Join(",",
                    frame.FrameIndex,
                    frame.TotalUsedMemoryBytes,
                    frame.GcUsedMemoryBytes,
                    frame.GcAllocatedInFrameBytes,
                    frame.GcAllocCountInFrame
                ));
            }

            File.WriteAllText(filePath, sb.ToString());

            // Also export GC allocation details if available
            if (data.GcAllocations.Count > 0)
            {
                var gcFilePath = Path.Combine(sessionPath, "gc_allocations.csv");
                var gcSb = new StringBuilder();
                gcSb.AppendLine("frameIndex,allocCount,allocBytes,functionName,category");

                foreach (var gc in data.GcAllocations)
                {
                    var functionName = gc.FunctionName?.Replace(",", ";") ?? "Unknown";
                    var category = gc.Category?.Replace(",", ";") ?? "Unknown";
                    gcSb.AppendLine($"{gc.FrameIndex},{gc.AllocCount},{gc.AllocBytes},{functionName},{category}");
                }

                File.WriteAllText(gcFilePath, gcSb.ToString());
            }
        }

        private void ExportRenderingData(string sessionPath, CaptureData data)
        {
            var filePath = Path.Combine(sessionPath, "rendering.csv");
            var sb = new StringBuilder();

            // Header with rendering metrics
            sb.AppendLine("frameIndex,drawCalls,batches,triangles,vertices,setPassCalls");

            foreach (var frame in data.Frames)
            {
                sb.AppendLine(string.Join(",",
                    frame.FrameIndex,
                    frame.DrawCalls,
                    frame.Batches,
                    frame.Triangles,
                    frame.Vertices,
                    frame.SetPassCalls
                ));
            }

            File.WriteAllText(filePath, sb.ToString());
        }

        private void ExportAssetInventory(string sessionPath, CaptureData data)
        {
            if (data.AssetInventory == null)
            {
                return;
            }

            var inventory = data.AssetInventory;

            // Export textures
            if (inventory.Textures.Count > 0)
            {
                var filePath = Path.Combine(sessionPath, "textures.csv");
                var sb = new StringBuilder();
                sb.AppendLine("name,assetPath,width,height,format,memoryBytes,hasMipmaps,readWriteEnabled,filterMode,wrapMode,anisoLevel");

                foreach (var tex in inventory.Textures)
                {
                    sb.AppendLine(string.Join(",",
                        EscapeCsv(tex.Name),
                        EscapeCsv(tex.AssetPath),
                        tex.Width,
                        tex.Height,
                        tex.Format,
                        tex.MemoryBytes,
                        tex.HasMipmaps,
                        tex.ReadWriteEnabled,
                        tex.FilterMode,
                        tex.WrapMode,
                        tex.AnisoLevel
                    ));
                }

                File.WriteAllText(filePath, sb.ToString());
            }

            // Export meshes
            if (inventory.Meshes.Count > 0)
            {
                var filePath = Path.Combine(sessionPath, "meshes.csv");
                var sb = new StringBuilder();
                sb.AppendLine("name,assetPath,vertexCount,triangleCount,subMeshCount,memoryBytes,readWriteEnabled,boundsSize,hasNormals,hasTangents,hasUV,hasColors");

                foreach (var mesh in inventory.Meshes)
                {
                    sb.AppendLine(string.Join(",",
                        EscapeCsv(mesh.Name),
                        EscapeCsv(mesh.AssetPath),
                        mesh.VertexCount,
                        mesh.TriangleCount,
                        mesh.SubMeshCount,
                        mesh.MemoryBytes,
                        mesh.ReadWriteEnabled,
                        EscapeCsv(mesh.BoundsSize),
                        mesh.HasNormals,
                        mesh.HasTangents,
                        mesh.HasUV,
                        mesh.HasColors
                    ));
                }

                File.WriteAllText(filePath, sb.ToString());
            }

            // Export materials
            if (inventory.Materials.Count > 0)
            {
                var filePath = Path.Combine(sessionPath, "materials.csv");
                var sb = new StringBuilder();
                sb.AppendLine("name,assetPath,shaderName,renderQueue,gpuInstancingEnabled,passCount,textureCount");

                foreach (var mat in inventory.Materials)
                {
                    sb.AppendLine(string.Join(",",
                        EscapeCsv(mat.Name),
                        EscapeCsv(mat.AssetPath),
                        EscapeCsv(mat.ShaderName),
                        mat.RenderQueue,
                        mat.GpuInstancingEnabled,
                        mat.PassCount,
                        mat.TextureCount
                    ));
                }

                File.WriteAllText(filePath, sb.ToString());
            }

            // Export audio clips
            if (inventory.AudioClips.Count > 0)
            {
                var filePath = Path.Combine(sessionPath, "audio.csv");
                var sb = new StringBuilder();
                sb.AppendLine("name,assetPath,durationSeconds,channels,frequency,memoryBytes,loadType,loadInBackground,preload");

                foreach (var audio in inventory.AudioClips)
                {
                    sb.AppendLine(string.Join(",",
                        EscapeCsv(audio.Name),
                        EscapeCsv(audio.AssetPath),
                        audio.DurationSeconds.ToString("F3"),
                        audio.Channels,
                        audio.Frequency,
                        audio.MemoryBytes,
                        audio.LoadType,
                        audio.LoadInBackground,
                        audio.Preload
                    ));
                }

                File.WriteAllText(filePath, sb.ToString());
            }

            // Export shaders
            if (inventory.Shaders.Count > 0)
            {
                var filePath = Path.Combine(sessionPath, "shaders.csv");
                var sb = new StringBuilder();
                sb.AppendLine("name,passCount,isSupported,renderQueue,propertyCount");

                foreach (var shader in inventory.Shaders)
                {
                    sb.AppendLine(string.Join(",",
                        EscapeCsv(shader.Name),
                        shader.PassCount,
                        shader.IsSupported,
                        shader.RenderQueue,
                        shader.PropertyCount
                    ));
                }

                File.WriteAllText(filePath, sb.ToString());
            }

            // Export scene objects (top-level summary for analysis)
            if (inventory.SceneObjects.Count > 0)
            {
                var filePath = Path.Combine(sessionPath, "scene_objects.csv");
                var sb = new StringBuilder();
                sb.AppendLine("name,path,isStatic,isActive,layer,componentCount,hasRenderer,hasCollider,hasRigidbody,meshName,materialName");

                foreach (var obj in inventory.SceneObjects)
                {
                    sb.AppendLine(string.Join(",",
                        EscapeCsv(obj.Name),
                        EscapeCsv(obj.Path),
                        obj.IsStatic,
                        obj.IsActive,
                        EscapeCsv(obj.Layer),
                        obj.ComponentCount,
                        obj.HasRenderer,
                        obj.HasCollider,
                        obj.HasRigidbody,
                        EscapeCsv(obj.MeshName ?? ""),
                        EscapeCsv(obj.MaterialName ?? "")
                    ));
                }

                File.WriteAllText(filePath, sb.ToString());
            }

            // Export cameras
            if (inventory.Cameras.Count > 0)
            {
                var filePath = Path.Combine(sessionPath, "cameras.csv");
                var sb = new StringBuilder();
                sb.AppendLine("name,isActive,depth,clearFlags,renderingPath,fieldOfView,nearClip,farClip,useOcclusionCulling");

                foreach (var cam in inventory.Cameras)
                {
                    sb.AppendLine(string.Join(",",
                        EscapeCsv(cam.Name),
                        cam.IsActive,
                        cam.Depth.ToString("F2"),
                        cam.ClearFlags,
                        cam.RenderingPath,
                        cam.FieldOfView.ToString("F1"),
                        cam.NearClip.ToString("F3"),
                        cam.FarClip.ToString("F1"),
                        cam.UseOcclusionCulling
                    ));
                }

                File.WriteAllText(filePath, sb.ToString());
            }

            // Export lights
            if (inventory.Lights.Count > 0)
            {
                var filePath = Path.Combine(sessionPath, "lights.csv");
                var sb = new StringBuilder();
                sb.AppendLine("name,type,isActive,shadowType,intensity,range,renderMode");

                foreach (var light in inventory.Lights)
                {
                    sb.AppendLine(string.Join(",",
                        EscapeCsv(light.Name),
                        light.Type,
                        light.IsActive,
                        light.ShadowType,
                        light.Intensity.ToString("F2"),
                        light.Range.ToString("F2"),
                        light.RenderMode
                    ));
                }

                File.WriteAllText(filePath, sb.ToString());
            }
        }

        private string EscapeCsv(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return "";
            }

            if (value.Contains(",") || value.Contains("\"") || value.Contains("\n"))
            {
                return "\"" + value.Replace("\"", "\"\"") + "\"";
            }

            return value;
        }

        private void ExportSummary(string sessionPath, CaptureData data)
        {
            var filePath = Path.Combine(sessionPath, "summary.txt");
            var sb = new StringBuilder();

            // Calculate statistics
            var avgFrameTime = data.Frames.Average(f => f.DurationMs);
            var maxFrameTime = data.Frames.Max(f => f.DurationMs);
            var minFrameTime = data.Frames.Min(f => f.DurationMs);
            var avgFps = 1000f / avgFrameTime;

            var framesOver16ms = data.Frames.Count(f => f.DurationMs > 16.67f);
            var framesOver33ms = data.Frames.Count(f => f.DurationMs > 33.33f);

            var totalGcAlloc = data.Frames.Sum(f => f.GcAllocatedInFrameBytes);
            var avgGcPerFrame = data.Frames.Average(f => f.GcAllocatedInFrameBytes);
            var framesWithGc = data.Frames.Count(f => f.GcAllocatedInFrameBytes > 0);

            var avgDrawCalls = data.Frames.Average(f => f.DrawCalls);
            var maxDrawCalls = data.Frames.Max(f => f.DrawCalls);

            sb.AppendLine("FrameDoctor Profiler Export Summary");
            sb.AppendLine("===================================");
            sb.AppendLine();

            sb.AppendLine("Session Info");
            sb.AppendLine("------------");
            sb.AppendLine($"FrameDoctor Version: {FrameDoctorConstants.Version}");
            sb.AppendLine($"Export Time: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
            sb.AppendLine($"Unity Version: {data.UnityVersion ?? Application.unityVersion}");
            sb.AppendLine($"Platform: {data.Platform ?? Application.platform.ToString()}");
            sb.AppendLine($"Product Name: {data.ProductName ?? Application.productName}");
            sb.AppendLine();

            sb.AppendLine("Capture Statistics");
            sb.AppendLine("------------------");
            sb.AppendLine($"Total Frames: {data.TotalFrames}");
            sb.AppendLine($"Total Duration: {data.TotalDurationMs:F2}ms ({data.TotalDurationMs / 1000f:F2}s)");
            sb.AppendLine();

            sb.AppendLine("Frame Timing");
            sb.AppendLine("------------");
            sb.AppendLine($"Average Frame Time: {avgFrameTime:F2}ms ({avgFps:F1} FPS)");
            sb.AppendLine($"Min Frame Time: {minFrameTime:F2}ms");
            sb.AppendLine($"Max Frame Time: {maxFrameTime:F2}ms");
            sb.AppendLine($"Frames >16.67ms (60 FPS budget): {framesOver16ms} ({100f * framesOver16ms / data.TotalFrames:F1}%)");
            sb.AppendLine($"Frames >33.33ms (30 FPS budget): {framesOver33ms} ({100f * framesOver33ms / data.TotalFrames:F1}%)");
            sb.AppendLine();

            sb.AppendLine("Memory / GC");
            sb.AppendLine("-----------");
            sb.AppendLine($"Total GC Allocated: {FormatBytes(totalGcAlloc)}");
            sb.AppendLine($"Average GC per Frame: {FormatBytes((long)avgGcPerFrame)}");
            sb.AppendLine($"Frames with GC Allocation: {framesWithGc} ({100f * framesWithGc / data.TotalFrames:F1}%)");
            sb.AppendLine();

            sb.AppendLine("Rendering");
            sb.AppendLine("---------");
            sb.AppendLine($"Average Draw Calls: {avgDrawCalls:F1}");
            sb.AppendLine($"Max Draw Calls: {maxDrawCalls}");
            sb.AppendLine();

            // FrameDoctor overhead statistics
            var avgOverhead = data.Frames.Average(f => f.FrameDoctorOverheadMs);
            var maxOverhead = data.Frames.Max(f => f.FrameDoctorOverheadMs);
            var overheadPercent = avgFrameTime > 0 ? (avgOverhead / avgFrameTime) * 100f : 0f;
            sb.AppendLine("FrameDoctor Overhead");
            sb.AppendLine("--------------------");
            sb.AppendLine($"Average Overhead: {avgOverhead:F4}ms per frame");
            sb.AppendLine($"Max Overhead: {maxOverhead:F4}ms");
            sb.AppendLine($"Overhead as % of avg frame: {overheadPercent:F2}%");
            sb.AppendLine();

            // Asset inventory summary
            if (data.AssetInventory != null)
            {
                var inv = data.AssetInventory;
                sb.AppendLine("Asset Inventory");
                sb.AppendLine("---------------");
                sb.AppendLine($"Scene: {inv.SceneName}");
                sb.AppendLine($"Textures: {inv.Textures.Count} ({FormatBytes(inv.TotalTextureMemoryBytes)})");
                sb.AppendLine($"Meshes: {inv.Meshes.Count} ({FormatBytes(inv.TotalMeshMemoryBytes)})");
                sb.AppendLine($"Materials: {inv.Materials.Count}");
                sb.AppendLine($"Shaders: {inv.Shaders.Count}");
                sb.AppendLine($"Audio Clips: {inv.AudioClips.Count} ({FormatBytes(inv.TotalAudioMemoryBytes)})");
                sb.AppendLine($"Scene Objects: {inv.SceneObjects.Count}");
                sb.AppendLine($"Cameras: {inv.Cameras.Count}");
                sb.AppendLine($"Lights: {inv.Lights.Count}");
                sb.AppendLine();
            }

            sb.AppendLine("Files Exported");
            sb.AppendLine("--------------");
            sb.AppendLine("- frame_timing.csv  (CPU/GPU timing per frame)");
            sb.AppendLine("- memory.csv        (Memory and GC metrics per frame)");
            sb.AppendLine("- rendering.csv     (Draw calls, batches, triangles per frame)");
            if (data.GcAllocations.Count > 0)
            {
                sb.AppendLine("- gc_allocations.csv (GC allocation details)");
            }
            if (data.AssetInventory != null)
            {
                if (data.AssetInventory.Textures.Count > 0)
                    sb.AppendLine("- textures.csv      (Texture assets in scene)");
                if (data.AssetInventory.Meshes.Count > 0)
                    sb.AppendLine("- meshes.csv        (Mesh assets in scene)");
                if (data.AssetInventory.Materials.Count > 0)
                    sb.AppendLine("- materials.csv     (Material assets in scene)");
                if (data.AssetInventory.AudioClips.Count > 0)
                    sb.AppendLine("- audio.csv         (Audio clips in scene)");
                if (data.AssetInventory.Shaders.Count > 0)
                    sb.AppendLine("- shaders.csv       (Shaders in use)");
                if (data.AssetInventory.SceneObjects.Count > 0)
                    sb.AppendLine("- scene_objects.csv (GameObjects in scene)");
                if (data.AssetInventory.Cameras.Count > 0)
                    sb.AppendLine("- cameras.csv       (Camera settings)");
                if (data.AssetInventory.Lights.Count > 0)
                    sb.AppendLine("- lights.csv        (Light settings)");
            }
            sb.AppendLine("- summary.txt       (This file)");
            sb.AppendLine();

            sb.AppendLine("Next Steps");
            sb.AppendLine("----------");
            sb.AppendLine("Use these CSV files for your own analysis, or optionally upload them to");
            sb.AppendLine($"{FrameDoctorConstants.WebServiceUrl} for AI-powered performance recommendations.");

            File.WriteAllText(filePath, sb.ToString());
        }

        private string FormatBytes(long bytes)
        {
            if (bytes < 1024)
            {
                return $"{bytes} B";
            }
            if (bytes < 1024 * 1024)
            {
                return $"{bytes / 1024f:F2} KB";
            }
            if (bytes < 1024 * 1024 * 1024)
            {
                return $"{bytes / (1024f * 1024f):F2} MB";
            }
            return $"{bytes / (1024f * 1024f * 1024f):F2} GB";
        }

        private string GenerateSessionName()
        {
            // Format: ProjectName_YYYY_DD_MM_HHMM_UID
            var projectName = SanitizeFileName(Application.productName);
            if (string.IsNullOrEmpty(projectName))
            {
                projectName = "UnityProject";
            }

            var timestamp = DateTime.Now.ToString("yyyy_dd_MM_HHmm");
            var uid = Guid.NewGuid().ToString("N").Substring(0, 4);

            return $"{projectName}_{timestamp}_{uid}";
        }

        private string SanitizeFileName(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                return "";
            }

            // Remove invalid filename characters and replace spaces with underscores
            var invalidChars = Path.GetInvalidFileNameChars();
            var sanitized = new StringBuilder();

            foreach (var c in name)
            {
                if (Array.IndexOf(invalidChars, c) < 0)
                {
                    sanitized.Append(c == ' ' ? '_' : c);
                }
            }

            return sanitized.ToString();
        }
    }
}
