using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using Jovian.SaveSystem;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;

namespace Jovian.InGameLogging.Editor {
    public class GameLogViewerWindow : EditorWindow {
        private static readonly Regex richTextRegex = new(@"<[^>]+>", RegexOptions.Compiled);

        private Vector2 slotListScroll;
        private Vector2 logEntriesScroll;

        private List<SessionGroup> sessions = new();
        private SaveSlotEntry selectedSlot;
        private List<LogEntry> loadedEntries = new();
        private List<LogEntry> filteredEntries = new();
        private List<string> channelNames = new();
        private int selectedChannelIndex;
        private string savePath;

        private GUIStyle channelTagStyle;
        private GUIStyle timeStyle;
        private GUIStyle messageStyle;
        private GUIStyle slotButtonStyle;
        private GUIStyle noDataStyle;
        private bool stylesInitialized;

        [MenuItem("Jovian/In-Game Logging/Log Viewer")]
        public static void ShowWindow() {
            var window = GetWindow<GameLogViewerWindow>("In-Game Log Viewer");
            window.minSize = new Vector2(500, 400);
            window.Show();
        }

        private void OnEnable() {
            ScanSaves();
        }

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

            channelTagStyle = new GUIStyle(EditorStyles.miniLabel) {
                fontStyle = FontStyle.Bold,
                normal = { textColor = new Color(0.5f, 0.8f, 1f) },
                fixedWidth = 120,
                alignment = TextAnchor.MiddleLeft
            };

            timeStyle = new GUIStyle(EditorStyles.miniLabel) {
                normal = { textColor = new Color(0.6f, 0.6f, 0.6f) },
                fixedWidth = 70,
                alignment = TextAnchor.MiddleRight
            };

            messageStyle = new GUIStyle(EditorStyles.label) {
                wordWrap = false,
                richText = false
            };

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

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

            stylesInitialized = true;
        }

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

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

            DrawSlotList();
            DrawChannelFilter();
            DrawLogEntries();
        }

        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", EditorStyles.boldLabel);
            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)) {
                        SelectSlot(slot);
                    }
                }
                EditorGUI.indentLevel--;
            }

            EditorGUILayout.EndScrollView();
            DrawSeparator();
        }

        private void DrawChannelFilter() {
            if(loadedEntries.Count == 0) {
                return;
            }

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Channel filter:", GUILayout.Width(90));
            var newIndex = EditorGUILayout.Popup(selectedChannelIndex, channelNames.ToArray());
            if(newIndex != selectedChannelIndex) {
                selectedChannelIndex = newIndex;
                ApplyChannelFilter();
            }
            EditorGUILayout.EndHorizontal();
        }

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

            if(filteredEntries.Count == 0) {
                EditorGUILayout.LabelField("No log entries in this save.", noDataStyle, GUILayout.ExpandHeight(true));
                return;
            }

            EditorGUILayout.LabelField($"{filteredEntries.Count} entries", EditorStyles.miniLabel);
            logEntriesScroll = EditorGUILayout.BeginScrollView(logEntriesScroll, GUILayout.ExpandHeight(true));

            foreach(var entry in filteredEntries) {
                EditorGUILayout.BeginHorizontal();
                EditorGUILayout.LabelField($"[{entry.channel}]", channelTagStyle);
                EditorGUILayout.LabelField(FormatTime(entry.gameTime), timeStyle);
                EditorGUILayout.LabelField(StripRichText(entry.message), messageStyle);
                EditorGUILayout.EndHorizontal();
            }

            EditorGUILayout.EndScrollView();
        }

        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;
            loadedEntries.Clear();
            filteredEntries.Clear();
            channelNames.Clear();
            selectedChannelIndex = 0;

            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<SaveSlotEntry>>();
                foreach(var slot in index.slots) {
                    if(!slotsBySession.ContainsKey(slot.sessionId)) {
                        slotsBySession[slot.sessionId] = new List<SaveSlotEntry>();
                    }
                    slotsBySession[slot.sessionId].Add(new SaveSlotEntry {
                        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($"[GameLogViewer] Failed to read save index: {e.Message}");
            }
        }

        private void SelectSlot(SaveSlotEntry slot) {
            selectedSlot = slot;
            loadedEntries.Clear();
            filteredEntries.Clear();
            channelNames.Clear();
            channelNames.Add("All");
            selectedChannelIndex = 0;

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

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

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

                // Navigate via JToken to avoid coupling to NoxSavedDataSet
                var gameLogToken = envelope.payload["gameLogData"];
                if(gameLogToken == null) {
                    return;
                }

                var entriesToken = gameLogToken["entries"];
                if(entriesToken == null) {
                    return;
                }

                var logData = entriesToken.ToObject<List<LogEntry>>();
                if(logData == null) {
                    return;
                }

                loadedEntries.AddRange(logData);

                // Build channel list
                var uniqueChannels = new HashSet<string>();
                foreach(var entry in loadedEntries) {
                    var channelId = entry.channel.Id;
                    if(!string.IsNullOrEmpty(channelId) && uniqueChannels.Add(channelId)) {
                        channelNames.Add(channelId);
                    }
                }

                ApplyChannelFilter();
            }
            catch(Exception e) {
                Debug.LogError($"[GameLogViewer] Failed to load save: {e.Message}");
            }
        }

        private void ApplyChannelFilter() {
            filteredEntries.Clear();
            if(selectedChannelIndex == 0) {
                filteredEntries.AddRange(loadedEntries);
            }
            else {
                var channelId = channelNames[selectedChannelIndex];
                foreach(var entry in loadedEntries) {
                    if(entry.channel.Id == channelId) {
                        filteredEntries.Add(entry);
                    }
                }
            }
        }

        private static string StripRichText(string text) {
            return string.IsNullOrEmpty(text) ? string.Empty : richTextRegex.Replace(text, string.Empty);
        }

        private static string FormatTime(float gameTime) {
            var minutes = (int)(gameTime / 60f);
            var seconds = gameTime % 60f;
            return $"{minutes}:{seconds:00.0}";
        }

        // Internal types for deserialization -- mirrors save system index structure

        [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<SaveSlotEntry> slots;
        }

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