using System;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
using UnityEditor;
using Unity.Profiling;
using Debug = UnityEngine.Debug;

namespace FrameDoctor.Editor.Logic
{
    // Service for capturing Unity Profiler data using ProfilerRecorder API.
    // Captures real frame timing, memory, and rendering statistics.
    public class ProfilerCaptureService : IDisposable
    {
        private bool _isCapturing;
        private bool _isDisposed;
        private float _captureStartTime;
        private int _startFrameCount;

        private readonly List<FrameData> _capturedFrames;
        private readonly List<GcAllocation> _gcAllocations;
        private readonly List<MarkerData> _markerSamples;

        // ProfilerRecorders for various metrics
        private ProfilerRecorder _mainThreadTimeRecorder;
        private ProfilerRecorder _renderThreadTimeRecorder;
        private ProfilerRecorder _gpuFrameTimeRecorder;

        // Memory recorders
        private ProfilerRecorder _totalUsedMemoryRecorder;
        private ProfilerRecorder _gcUsedMemoryRecorder;
        private ProfilerRecorder _gcAllocatedRecorder;
        private ProfilerRecorder _gcAllocationCountRecorder;

        // Rendering recorders
        private ProfilerRecorder _drawCallsRecorder;
        private ProfilerRecorder _batchesRecorder;
        private ProfilerRecorder _trianglesRecorder;
        private ProfilerRecorder _verticesRecorder;
        private ProfilerRecorder _setPassCallsRecorder;

        // Subsystem recorders
        private ProfilerRecorder _scriptsRecorder;
        private ProfilerRecorder _physicsRecorder;
        private ProfilerRecorder _animationRecorder;
        private ProfilerRecorder _renderingRecorder;

        // Overhead measurement
        private readonly Stopwatch _captureStopwatch = new Stopwatch();

        public bool IsCapturing => _isCapturing;

        public event Action<FrameData> OnFrameCaptured;

        public ProfilerCaptureService()
        {
            _capturedFrames = new List<FrameData>();
            _gcAllocations = new List<GcAllocation>();
            _markerSamples = new List<MarkerData>();
        }

        // Start capturing profiler data.
        public void StartCapture()
        {
            if (_isCapturing)
            {
                Debug.LogWarning("[FrameDoctor] Capture already in progress");
                return;
            }

            _capturedFrames.Clear();
            _gcAllocations.Clear();
            _markerSamples.Clear();

            InitializeRecorders();

            _captureStartTime = Time.realtimeSinceStartup;
            _startFrameCount = Time.frameCount;
            _isCapturing = true;

            EditorApplication.update += CaptureFrame;

            FrameDoctor.Editor.Config.FrameDoctorSettings.Log("[FrameDoctor] Started profiler capture");
        }

        // Stop capturing profiler data.
        public void StopCapture()
        {
            if (!_isCapturing)
            {
                Debug.LogWarning("[FrameDoctor] No capture in progress");
                return;
            }

            EditorApplication.update -= CaptureFrame;
            _isCapturing = false;

            DisposeRecorders();

            FrameDoctor.Editor.Config.FrameDoctorSettings.Log($"[FrameDoctor] Stopped capture - recorded {_capturedFrames.Count} frames");
        }

        private void InitializeRecorders()
        {
            // Frame timing
            _mainThreadTimeRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Internal, "Main Thread", 1);
            _renderThreadTimeRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Internal, "Render Thread", 1);
            _gpuFrameTimeRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Internal, "GPU Frame Time", 1);

            // Memory - use SystemUsedMemory and GC markers
            _totalUsedMemoryRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Memory, "System Used Memory", 1);
            _gcUsedMemoryRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Memory, "GC Used Memory", 1);
            _gcAllocatedRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Memory, "GC Allocated In Frame", 1);
            _gcAllocationCountRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Memory, "GC Allocation In Frame Count", 1);

            // Rendering stats
            _drawCallsRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Draw Calls Count", 1);
            _batchesRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Batches Count", 1);
            _trianglesRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Triangles Count", 1);
            _verticesRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Vertices Count", 1);
            _setPassCallsRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, "SetPass Calls Count", 1);

            // Subsystems
            _scriptsRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Scripts, "BehaviourUpdate", 1);
            _physicsRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Physics, "Physics.Simulate", 1);
            _animationRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Animation, "Animators.Update", 1);
            _renderingRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Camera.Render", 1);
        }

        private void DisposeRecorders()
        {
            _mainThreadTimeRecorder.Dispose();
            _renderThreadTimeRecorder.Dispose();
            _gpuFrameTimeRecorder.Dispose();

            _totalUsedMemoryRecorder.Dispose();
            _gcUsedMemoryRecorder.Dispose();
            _gcAllocatedRecorder.Dispose();
            _gcAllocationCountRecorder.Dispose();

            _drawCallsRecorder.Dispose();
            _batchesRecorder.Dispose();
            _trianglesRecorder.Dispose();
            _verticesRecorder.Dispose();
            _setPassCallsRecorder.Dispose();

            _scriptsRecorder.Dispose();
            _physicsRecorder.Dispose();
            _animationRecorder.Dispose();
            _renderingRecorder.Dispose();
        }

        private void CaptureFrame()
        {
            if (!_isCapturing || !EditorApplication.isPlaying)
            {
                return;
            }

            _captureStopwatch.Restart();

            var frameData = new FrameData
            {
                FrameIndex = Time.frameCount - _startFrameCount,
                StartTimeMs = (Time.realtimeSinceStartup - _captureStartTime) * 1000f,
                DurationMs = Time.deltaTime * 1000f,

                // Frame timing (convert from nanoseconds to milliseconds)
                CpuFrameTimeMs = Time.deltaTime * 1000f,
                CpuMainThreadMs = GetRecorderValueMs(_mainThreadTimeRecorder),
                CpuRenderThreadMs = GetRecorderValueMs(_renderThreadTimeRecorder),
                GpuFrameTimeMs = GetRecorderValueMs(_gpuFrameTimeRecorder),

                // Memory
                TotalUsedMemoryBytes = GetRecorderValue(_totalUsedMemoryRecorder),
                GcUsedMemoryBytes = GetRecorderValue(_gcUsedMemoryRecorder),
                GcAllocatedInFrameBytes = GetRecorderValue(_gcAllocatedRecorder),
                GcAllocCountInFrame = (int)GetRecorderValue(_gcAllocationCountRecorder),

                // Rendering
                DrawCalls = (int)GetRecorderValue(_drawCallsRecorder),
                Batches = (int)GetRecorderValue(_batchesRecorder),
                Triangles = (int)GetRecorderValue(_trianglesRecorder),
                Vertices = (int)GetRecorderValue(_verticesRecorder),
                SetPassCalls = (int)GetRecorderValue(_setPassCallsRecorder),

                // Subsystems
                ScriptsMs = GetRecorderValueMs(_scriptsRecorder),
                PhysicsMs = GetRecorderValueMs(_physicsRecorder),
                AnimationMs = GetRecorderValueMs(_animationRecorder),
                RenderingMs = GetRecorderValueMs(_renderingRecorder)
            };

            _captureStopwatch.Stop();
            frameData.FrameDoctorOverheadMs = (float)_captureStopwatch.Elapsed.TotalMilliseconds;

            _capturedFrames.Add(frameData);

            // Track GC allocations per frame
            if (frameData.GcAllocatedInFrameBytes > 0)
            {
                _gcAllocations.Add(new GcAllocation
                {
                    FrameIndex = frameData.FrameIndex,
                    AllocBytes = frameData.GcAllocatedInFrameBytes,
                    AllocCount = frameData.GcAllocCountInFrame,
                    FunctionName = "Frame Total",
                    Category = "GC"
                });
            }

            OnFrameCaptured?.Invoke(frameData);
        }

        private long GetRecorderValue(ProfilerRecorder recorder)
        {
            if (!recorder.Valid || recorder.Count == 0)
            {
                return 0;
            }

            return recorder.LastValue;
        }

        private float GetRecorderValueMs(ProfilerRecorder recorder)
        {
            // ProfilerRecorder returns time in nanoseconds
            return GetRecorderValue(recorder) / 1_000_000f;
        }

        // Get the captured profiler data.
        public CaptureData GetCaptureData()
        {
            float totalDuration = 0f;
            if (_capturedFrames.Count > 0)
            {
                var lastFrame = _capturedFrames[_capturedFrames.Count - 1];
                totalDuration = lastFrame.StartTimeMs + lastFrame.DurationMs;
            }

            // Collect asset inventory from current scene
            var inventoryService = new AssetInventoryService();
            var assetInventory = inventoryService.CollectSceneAssets();

            return new CaptureData
            {
                Frames = new List<FrameData>(_capturedFrames),
                GcAllocations = new List<GcAllocation>(_gcAllocations),
                Markers = new List<MarkerData>(_markerSamples),
                TotalFrames = _capturedFrames.Count,
                TotalDurationMs = totalDuration,
                UnityVersion = Application.unityVersion,
                Platform = Application.platform.ToString(),
                ProductName = Application.productName,
                AssetInventory = assetInventory
            };
        }

        public void Dispose()
        {
            if (_isDisposed)
            {
                return;
            }

            if (_isCapturing)
            {
                StopCapture();
            }

            OnFrameCaptured = null;
            _isDisposed = true;
        }
    }
}
