using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;

namespace Jovian.SaveSystem.Editor {
    public class SaveDataViewerWindow : EditorWindow {
        private Vector2 slotListScroll;
        private Vector2 dataScroll;

        private List<SessionGroup> sessions = new();
        private SlotEntry selectedSlot;
        private string savePath;

        // Parsed data for the selected slot
        private SaveEnvelope loadedEnvelope;
        private List<PayloadField> payloadFields = new();

        private GUIStyle labelStyle;
        private GUIStyle valueStyle;
        private GUIStyle headerStyle;
        private GUIStyle slotButtonStyle;
        private GUIStyle noDataStyle;
        private GUIStyle metaKeyStyle;
        private GUIStyle metaValueStyle;
        private bool stylesInitialized;

        [MenuItem("Jovian/Save System/Save Data Viewer")]
        public static void ShowWindow() {
            var window = GetWindow<SaveDataViewerWindow>("Save Data Viewer");
            window.minSize = new Vector2(550, 450);
            window.Show();
        }

        private void OnEnable() {
            ScanSaves();
        }

        private void InitStyles() {
            if(stylesInitialized) {
                return;
            }

            headerStyle = new GUIStyle(EditorStyles.boldLabel) {
                fontSize = 12
            };

            labelStyle = new GUIStyle(EditorStyles.label) {
                fontStyle = FontStyle.Bold,
                normal = { textColor = new Color(0.55f, 0.8f, 1f) }
            };

            valueStyle = new GUIStyle(EditorStyles.label) {
                wordWrap = true
            };

            slotButtonStyle = new GUIStyle(EditorStyles.toolbarButton) {
                alignment = TextAnchor.MiddleLeft,
                fixedHeight = 22
            };

            noDataStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel) {
                fontSize = 12,
                wordWrap = true
            };

            metaKeyStyle = new GUIStyle(EditorStyles.miniLabel) {
                fontStyle = FontStyle.Bold,
                fixedWidth = 100
            };

            metaValueStyle = new GUIStyle(EditorStyles.miniLabel);

            stylesInitialized = true;
        }

        private void OnGUI() {
            InitStyles();
            DrawToolbar();

            if(sessions.Count == 0) {
                EditorGUILayout.LabelField("No save files found.\nPlay the game and save to generate data.",
                    noDataStyle, GUILayout.ExpandHeight(true));
                return;
            }

            DrawSlotList();
            DrawSaveData();
        }

        private void DrawToolbar() {
            EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
            GUILayout.FlexibleSpace();
            if(GUILayout.Button("Refresh", EditorStyles.toolbarButton, GUILayout.Width(60))) {
                ScanSaves();
            }
            EditorGUILayout.EndHorizontal();
        }

        private void DrawSlotList() {
            EditorGUILayout.LabelField("Save Slots", headerStyle);
            slotListScroll = EditorGUILayout.BeginScrollView(slotListScroll, GUILayout.MaxHeight(200));

            foreach(var session in sessions) {
                session.foldout = EditorGUILayout.Foldout(session.foldout, session.displayName, true);
                if(!session.foldout) {
                    continue;
                }

                EditorGUI.indentLevel++;
                foreach(var slot in session.slots) {
                    var isSelected = selectedSlot != null && selectedSlot.filePath == slot.filePath;
                    var style = new GUIStyle(slotButtonStyle);
                    if(isSelected) {
                        style.fontStyle = FontStyle.Bold;
                    }

                    if(GUILayout.Button($"  {slot.displayName}", style)) {
                        LoadSlot(slot);
                    }
                }
                EditorGUI.indentLevel--;
            }

            EditorGUILayout.EndScrollView();
            DrawSeparator();
        }

        private void DrawSaveData() {
            if(selectedSlot == null) {
                EditorGUILayout.LabelField("Select a save slot to view its data.", noDataStyle, GUILayout.ExpandHeight(true));
                return;
            }

            if(loadedEnvelope == null) {
                EditorGUILayout.LabelField("Failed to load save data.", noDataStyle, GUILayout.ExpandHeight(true));
                return;
            }

            // Envelope metadata
            EditorGUILayout.LabelField("Envelope", headerStyle);
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Version:", metaKeyStyle);
            EditorGUILayout.LabelField(loadedEnvelope.version.ToString(), metaValueStyle);
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Slot Type:", metaKeyStyle);
            EditorGUILayout.LabelField(loadedEnvelope.slotType.ToString(), metaValueStyle);
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Timestamp:", metaKeyStyle);
            var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(loadedEnvelope.timestampUtc).LocalDateTime;
            EditorGUILayout.LabelField(timestamp.ToString("yyyy-MM-dd HH:mm:ss"), metaValueStyle);
            EditorGUILayout.EndHorizontal();

            DrawSeparator();
            EditorGUILayout.Space(4);
            EditorGUILayout.LabelField("Payload", headerStyle);

            if(payloadFields.Count == 0) {
                EditorGUILayout.LabelField("(empty payload)", noDataStyle);
                return;
            }

            dataScroll = EditorGUILayout.BeginScrollView(dataScroll, GUILayout.ExpandHeight(true));

            foreach(var field in payloadFields) {
                DrawPayloadField(field, 0);
            }

            EditorGUILayout.EndScrollView();
        }

        private void DrawPayloadField(PayloadField field, int depth) {
            if(field.children != null && field.children.Count > 0) {
                EditorGUI.indentLevel = depth;
                field.foldout = EditorGUILayout.Foldout(field.foldout, field.key, true, EditorStyles.foldoutHeader);
                if(field.foldout) {
                    foreach(var child in field.children) {
                        DrawPayloadField(child, depth + 1);
                    }
                }
            }
            else {
                EditorGUI.indentLevel = depth;
                EditorGUILayout.BeginHorizontal();
                EditorGUILayout.LabelField(field.key, labelStyle, GUILayout.Width(180));
                EditorGUILayout.LabelField(field.value, valueStyle);
                EditorGUILayout.EndHorizontal();
            }
        }

        private void DrawSeparator() {
            var rect = EditorGUILayout.GetControlRect(false, 1);
            EditorGUI.DrawRect(rect, new Color(0.3f, 0.3f, 0.3f));
        }

        private void ScanSaves() {
            sessions.Clear();
            selectedSlot = null;
            loadedEnvelope = null;
            payloadFields.Clear();

            var settings = SaveSystemSettings.Load();
            savePath = Path.Combine(Application.persistentDataPath, settings.saveDirectoryName);
            var indexPath = Path.Combine(savePath, "index.json");

            if(!File.Exists(indexPath)) {
                return;
            }

            try {
                var indexJson = File.ReadAllText(indexPath);
                var index = JsonConvert.DeserializeObject<SaveIndex>(indexJson);
                if(index?.sessions == null || index.slots == null) {
                    return;
                }

                var slotsBySession = new Dictionary<string, List<SlotEntry>>();
                foreach(var slot in index.slots) {
                    if(!slotsBySession.ContainsKey(slot.sessionId)) {
                        slotsBySession[slot.sessionId] = new List<SlotEntry>();
                    }
                    slotsBySession[slot.sessionId].Add(new SlotEntry {
                        sessionId = slot.sessionId,
                        displayName = slot.DisplayLabel + "  " + DateTimeOffset.FromUnixTimeMilliseconds(slot.timestampUtc).LocalDateTime.ToString("HH:mm"),
                        filePath = slot.filePath,
                        timestampUtc = slot.timestampUtc
                    });
                }

                foreach(var session in index.sessions) {
                    if(!slotsBySession.TryGetValue(session.sessionId, out var slots)) {
                        continue;
                    }
                    slots.Sort((a, b) => b.timestampUtc.CompareTo(a.timestampUtc));
                    sessions.Add(new SessionGroup {
                        sessionId = session.sessionId,
                        displayName = $"Session  ({DateTimeOffset.FromUnixTimeMilliseconds(session.lastSaveDateUtc).LocalDateTime:yyyy-MM-dd HH:mm})",
                        foldout = true,
                        slots = slots
                    });
                }

                sessions.Sort((a, b) => string.Compare(b.sessionId, a.sessionId, StringComparison.Ordinal));
            }
            catch(Exception e) {
                Debug.LogError($"[SaveDataViewer] Failed to read save index: {e.Message}");
            }
        }

        private void LoadSlot(SlotEntry slot) {
            selectedSlot = slot;
            loadedEnvelope = null;
            payloadFields.Clear();

            var fullPath = Path.Combine(savePath, slot.filePath);
            if(!File.Exists(fullPath)) {
                Debug.LogWarning($"[SaveDataViewer] Save file not found: {fullPath}");
                return;
            }

            try {
                var bytes = File.ReadAllBytes(fullPath);
                var serializer = new JsonSaveSerializer();
                loadedEnvelope = serializer.Deserialize<SaveEnvelope>(bytes);

                if(loadedEnvelope?.payload == null) {
                    return;
                }

                payloadFields = ParseToken(loadedEnvelope.payload);
            }
            catch(Exception e) {
                Debug.LogError($"[SaveDataViewer] Failed to load save: {e.Message}");
            }
        }

        private static List<PayloadField> ParseToken(JToken token) {
            var fields = new List<PayloadField>();

            if(token is JObject obj) {
                foreach(var property in obj.Properties()) {
                    fields.Add(ParseProperty(property.Name, property.Value));
                }
            }
            else if(token is JArray arr) {
                for(var i = 0; i < arr.Count; i++) {
                    fields.Add(ParseProperty($"[{i}]", arr[i]));
                }
            }

            return fields;
        }

        private static PayloadField ParseProperty(string key, JToken value) {
            switch(value.Type) {
                case JTokenType.Object: {
                    var children = ParseToken(value);
                    return new PayloadField { key = key, value = $"{{{children.Count} fields}}", children = children, foldout = false };
                }
                case JTokenType.Array: {
                    var arr = (JArray)value;
                    var children = new List<PayloadField>();
                    for(var i = 0; i < arr.Count; i++) {
                        children.Add(ParseProperty($"[{i}]", arr[i]));
                    }
                    return new PayloadField { key = $"{key} ({arr.Count})", value = $"[{arr.Count} items]", children = children, foldout = false };
                }
                case JTokenType.Null:
                    return new PayloadField { key = key, value = "null" };
                default:
                    return new PayloadField { key = key, value = value.ToString() };
            }
        }

        // Internal types

        [Serializable]
        private sealed class SaveIndex {
            public List<SaveSessionInfo> sessions;
            public List<SaveSlotInfo> slots;
        }

        private sealed class SessionGroup {
            public string sessionId;
            public string displayName;
            public bool foldout;
            public List<SlotEntry> slots;
        }

        private sealed class SlotEntry {
            public string sessionId;
            public string displayName;
            public string filePath;
            public long timestampUtc;
        }

        private sealed class PayloadField {
            public string key;
            public string value;
            public List<PayloadField> children;
            public bool foldout;
        }
    }
}
