using System.Diagnostics;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Util;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.Text;

namespace HellionChat.Ui.SettingsTabs;

internal sealed class Database : ISettingsTab
{
    private Plugin Plugin { get; }
    private Configuration Mutable { get; }

    public string Name => HellionStrings.Settings_Tab_Database + "###tabs-database";

    internal Database(Plugin plugin, Configuration mutable)
    {
        Plugin = plugin;
        Mutable = mutable;
    }

    private bool ShowAdvanced;

    private long DatabaseLastRefreshTicks;
    private long DatabaseSize;
    private long DatabaseLogSize;
    private int DatabaseMessageCount;

    public void Draw(bool changed)
    {
        // Shift-on-open keeps the Advanced tools available without a permanent
        // toggle in the UI, mirroring upstream Chat 2 behaviour.
        if (changed)
            ShowAdvanced = ImGui.GetIO().KeyShift;

        DrawStorageSection();
        ImGui.Spacing();
        DrawViewerSection();
        ImGui.Spacing();
        DrawStatsSection();
    }

    private void DrawStorageSection()
    {
        using var tree = ImRaii.TreeNode(HellionStrings.Settings_Database_Storage_Heading);
        if (!tree.Success)
            return;

        using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
        {
            ImGui.Checkbox(Language.Options_DatabaseBattleMessages_Name, ref Mutable.DatabaseBattleMessages);
            ImGuiUtil.HelpMarker(Language.Options_DatabaseBattleMessages_Description);

            if (ImGui.Checkbox(Language.Options_LoadPreviousSession_Name, ref Mutable.LoadPreviousSession))
                if (Mutable.LoadPreviousSession)
                    Mutable.FilterIncludePreviousSessions = true;
            ImGuiUtil.HelpMarker(Language.Options_LoadPreviousSession_Description);

            if (ImGui.Checkbox(Language.Options_FilterIncludePreviousSessions_Name, ref Mutable.FilterIncludePreviousSessions))
                if (!Mutable.FilterIncludePreviousSessions)
                    Mutable.LoadPreviousSession = false;
            ImGuiUtil.HelpMarker(Language.Options_FilterIncludePreviousSessions_Description);

            var old = new FileInfo(Path.Join(Plugin.Interface.ConfigDirectory.FullName, "chat.db"));
            var migratedOld = new FileInfo(Path.Join(Plugin.Interface.ConfigDirectory.FullName, "chat-litedb.db"));
            if (old.Exists || migratedOld.Exists)
            {
                ImGui.Spacing();
                ImGui.Separator();
                ImGui.Spacing();

                ImGui.TextUnformatted(Language.Options_Database_Old_Heading);
                ImGui.Spacing();

                if (ImGuiUtil.CtrlShiftButton(Language.Options_Database_Old_Delete, Language.Options_Database_Old_Delete_Tooltip))
                {
                    try
                    {
                        // Delete both legacy files in one click — the previous if/else
                        // left the second file behind when both happened to exist.
                        if (old.Exists)
                            old.Delete();
                        if (migratedOld.Exists)
                            migratedOld.Delete();
                        WrapperUtil.AddNotification(Language.Options_Database_Old_Delete_Success, NotificationType.Success);
                    }
                    catch (Exception e)
                    {
                        Plugin.Log.Error(e, "Unable to delete old database");
                        WrapperUtil.AddNotification(Language.Options_Database_Old_Delete_Error, NotificationType.Error);
                    }
                }
            }
        }
    }

    private void DrawViewerSection()
    {
        using var tree = ImRaii.TreeNode(HellionStrings.Settings_Database_Viewer_Heading);
        if (!tree.Success)
            return;

        using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
        {
            // Refresh the database size and message count every 5 seconds to avoid
            // constant stat calls and spamming the database.
            if (DatabaseLastRefreshTicks + 5 * 1000 < Environment.TickCount64)
            {
                DatabaseSize = Plugin.MessageManager.Store.DatabaseSize();
                DatabaseLogSize = Plugin.MessageManager.Store.DatabaseLogSize();
                DatabaseMessageCount = Plugin.MessageManager.Store.MessageCount();
                DatabaseLastRefreshTicks = Environment.TickCount64;
            }

            // Copy the directory path instead of the file path so people can
            // paste it into their file explorer.
            ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_Path, MessageManager.DatabasePath()));
            if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
            {
                var path = Path.GetDirectoryName(MessageManager.DatabasePath());
                ImGui.SetClipboardText(path);
                WrapperUtil.AddNotification(Language.Options_Database_Metadata_CopyConfigPathNotification, NotificationType.Info);
            }

            if (ImGui.IsItemHovered())
            {
                ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
                ImGuiUtil.Tooltip(Language.Options_Database_Metadata_CopyConfigPath);
            }

            ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_Size, StringUtil.BytesToString(DatabaseSize)));
            if (ImGui.IsItemHovered())
                ImGuiUtil.Tooltip(StringUtil.BytesToString(DatabaseSize));

            ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_LogSize, StringUtil.BytesToString(DatabaseLogSize)));
            if (ImGui.IsItemHovered())
                ImGuiUtil.Tooltip(StringUtil.BytesToString(DatabaseLogSize));

            ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_MessageCount, DatabaseMessageCount));

            if (ImGuiUtil.CtrlShiftButton(Language.Options_ClearDatabase_Button, Language.Options_ClearDatabase_Tooltip))
            {
                Plugin.Log.Warning("Clearing messages from database");
                Plugin.MessageManager.Store.ClearMessages();
                Plugin.MessageManager.ClearAllTabs();

                // Refresh on next draw
                DatabaseLastRefreshTicks = 0;
                WrapperUtil.AddNotification(Language.Options_ClearDatabase_Success, NotificationType.Info);
            }
        }
    }

    private void DrawStatsSection()
    {
        if (!ShowAdvanced)
            return;

        using var tree = ImRaii.TreeNode(HellionStrings.Settings_Database_Stats_Heading);
        if (!tree.Success)
            return;

        using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
        {
            using var wrap = ImRaii.TextWrapPos(0.0f);

            ImGuiUtil.WarningText(Language.Options_Database_Advanced_Warning);
            if (ImGuiUtil.CtrlShiftButton("Perform maintenance", "Ctrl+Shift: MessageManager.Store.PerformMaintenance()"))
                Plugin.MessageManager.Store.PerformMaintenance();

            if (ImGuiUtil.CtrlShiftButton("Reload messages from database", "Ctrl+Shift: MessageManager.FilterAllTabs()"))
            {
                Plugin.MessageManager.ClearAllTabs();
                Plugin.MessageManager.FilterAllTabsAsync();
            }

            if (ImGuiUtil.CtrlShiftButton("Inject 10,000 messages", "Ctrl+Shift: creates 10,000 unique messages (async)"))
                new Thread(() => InsertMessages(10_000)).Start();
        }
    }

    private void InsertMessages(int count)
    {
        Plugin.Log.Info($"Inserting {count} messages due to user request");

        // Generate
        var stopwatch = Stopwatch.StartNew();
        var playerName = Plugin.PlayerState.CharacterName;
        var worldId = Plugin.PlayerState.HomeWorld.ValueNullable?.RowId ?? 0;
        var senderSource = new SeStringBuilder()
            .AddText("<")
            .Add(new PlayerPayload(playerName, worldId))
            .AddText("Random Message")
            .Add(RawPayload.LinkTerminator)
            .AddText(">: ")
            .Build();
        var senderChunks = ChunkUtil.ToChunks(senderSource, ChunkSource.Sender, ChatType.Debug).ToList();
        var messages = new List<Message>(count);
        for (var i = 0; i < count; i++)
        {
            var contentSource = new SeStringBuilder()
                .AddText("Random message payload - ")
                .AddItalics(Guid.NewGuid().ToString())
                .Build();
            var contentChunks = ChunkUtil.ToChunks(contentSource, ChunkSource.Content, ChatType.Debug).ToList();

            var chatCode = new ChatCode(XivChatType.Say, 0, 0);
            messages.Add(new Message(
                Guid.NewGuid(),
                Plugin.MessageManager.CurrentContentId,
                Plugin.MessageManager.CurrentContentId,
                DateTimeOffset.UtcNow,
                chatCode,
                senderChunks,
                contentChunks,
                senderSource,
                contentSource,
                Guid.Empty
            ));
        }

        var elapsedTicks = stopwatch.ElapsedTicks;
        stopwatch.Stop();
        Plugin.Log.Info($"Crafted {count} messages in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)");

        // Insert
        stopwatch = Stopwatch.StartNew();
        foreach (var message in messages)
            Plugin.MessageManager.Store.UpsertMessage(message);

        elapsedTicks = stopwatch.ElapsedTicks;
        stopwatch.Stop();
        Plugin.Log.Info($"Upserted {count} messages in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)");

        // Clear tabs during framework frame
        Plugin.Framework.Run(() =>
        {
            stopwatch = Stopwatch.StartNew();
            Plugin.MessageManager.ClearAllTabs();
            elapsedTicks = stopwatch.ElapsedTicks;
            stopwatch.Stop();
            Plugin.Log.Info($"Cleared {Plugin.Config.Tabs.Count} tabs in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)");
        }).Wait();

        // Fetch and filter during framework frame
        Plugin.Framework.Run(() =>
        {
            stopwatch = Stopwatch.StartNew();
            // Intentionally synchronous
            Plugin.MessageManager.FilterAllTabs();
            elapsedTicks = stopwatch.ElapsedTicks;
            stopwatch.Stop();
            Plugin.Log.Info($"Fetched and filtered all tabs in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)");
        }).Wait();
    }
}
