using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Newtonsoft.Json;
using Scriban;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Timers;

namespace SpotifyHonorific.Updaters;

public class Updater : IDisposable
{
    private Config Config { get; init; }
    private IFramework Framework { get; init; }
    private IDalamudPluginInterface PluginInterface { get; init; }
    private IPluginLog PluginLog { get; init; }
    private ICallGateSubscriber<int, string, object> SetCharacterTitleSubscriber { get; init; }
    private ICallGateSubscriber<int, object> ClearCharacterTitleSubscriber { get; init; }

    private Action? UpdateTitle { get; set; }
    private string? UpdatedTitleJson { get; set; }
    private UpdaterContext UpdaterContext { get; init; } = new();

    private Timer MediaCheckTimer { get; init; }
    private MediaActivity? CurrentMediaActivity { get; set; }

    public Updater(Config config, IFramework framwork, IDalamudPluginInterface pluginInterface, IPluginLog pluginLog)
    {
        Config = config;
        Framework = framwork;
        PluginInterface = pluginInterface;
        PluginLog = pluginLog;

        SetCharacterTitleSubscriber = PluginInterface.GetIpcSubscriber<int, string, object>("Honorific.SetCharacterTitle");
        ClearCharacterTitleSubscriber = PluginInterface.GetIpcSubscriber<int, object>("Honorific.ClearCharacterTitle");

        MediaCheckTimer = new Timer(2000);
        MediaCheckTimer.Elapsed += CheckMediaActivity;
        MediaCheckTimer.AutoReset = true;

        if (Config.Enabled)
        {
            Start();
        }

        Framework.Update += OnFrameworkUpdate;
    }

    public void Dispose()
    {
        Framework.Update -= OnFrameworkUpdate;
        MediaCheckTimer?.Stop();
        MediaCheckTimer?.Dispose();
        Framework.RunOnFrameworkThread(() =>
        {
            ClearCharacterTitleSubscriber.InvokeAction(0);
        });
    }

    public Task Enable(bool value)
    {
        return value ? Start() : Stop();
    }

    public Task Restart()
    {
        return Stop().ContinueWith(t => Start());
    }

    public Task Start()
    {
        if (Config.Enabled)
        {
            MediaCheckTimer.Start();
            PluginLog.Info("Media activity monitoring started");
        }
        return Task.CompletedTask;
    }

    public Task Stop()
    {
        MediaCheckTimer.Stop();
        Framework.RunOnFrameworkThread(() =>
        {
            ClearCharacterTitleSubscriber.InvokeAction(0);
        });
        PluginLog.Info("Media activity monitoring stopped");
        return Task.CompletedTask;
    }

    public string State()
    {
        return MediaCheckTimer.Enabled ? "Running" : "Stopped";
    }

    private void CheckMediaActivity(object sender, ElapsedEventArgs e)
    {
        try
        {
            var mediaActivity = GetCurrentMediaActivity();

            if (!MediaActivitiesEqual(CurrentMediaActivity, mediaActivity))
            {
                CurrentMediaActivity = mediaActivity;
                MediaActivityUpdated(mediaActivity);
            }
        }
        catch (Exception ex)
        {
            PluginLog.Error($"Error checking media activity: {ex.Message}");
        }
    }

    private MediaActivity? GetCurrentMediaActivity()
    {
        var spotifyTrack = GetSpotifyTrack();
        if (!string.IsNullOrEmpty(spotifyTrack))
        {
            string artist = "";
            string songName = spotifyTrack;
            var parts = spotifyTrack.Split(new[] { " - " }, 2, StringSplitOptions.None);
            if (parts.Length == 2)
            {
                artist = parts[0];
                songName = parts[1];
            }

            return new MediaActivity
            {
                Name = "Spotify (V1)",
                SongName = songName,
                Artist = artist,
                Type = ActivityType.Listening
            };
        }

        return null;
    }

    private string? GetSpotifyTrack()
    {
        try
        {
            var spotifyProcesses = Process.GetProcessesByName("Spotify");
            var trackTitles = new List<string>();

            foreach (var process in spotifyProcesses)
            {
                EnumWindows((hWnd, lParam) =>
                {
                    uint processId;
                    GetWindowThreadProcessId(hWnd, out processId);

                    if (processId == process.Id)
                    {
                        var title = GetWindowTitle(hWnd);
                        if (!string.IsNullOrEmpty(title) &&
                            title != "Spotify" &&
                            title != "Spotify Premium" &&
                            title != "Spotify Free" &&
                            title.Contains(" - "))
                        {
                            trackTitles.Add(title);
                        }
                    }
                    return true;
                }, IntPtr.Zero);
            }

            return trackTitles.OrderByDescending(t => t.Length).FirstOrDefault();
        }
        catch (Exception ex)
        {
            PluginLog.Debug($"Error getting Spotify track: {ex.Message}");
        }
        return null;
    }

    private string GetWindowTitle(IntPtr hWnd)
    {
        const int nChars = 256;
        var buff = new StringBuilder(nChars);
        if (GetWindowTextW(hWnd, buff, nChars) > 0)
        {
            return buff.ToString();
        }
        return string.Empty;
    }


    [DllImport("user32.dll")]
    private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);

    [DllImport("user32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern int GetWindowTextW(IntPtr hWnd, StringBuilder text, int count);

    private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

    private bool MediaActivitiesEqual(MediaActivity? a, MediaActivity? b)
    {
        if (a == null && b == null) return true;
        if (a == null || b == null) return false;
        return a.Name == b.Name && a.SongName == b.SongName && a.Type == b.Type;
    }

    private void MediaActivityUpdated(MediaActivity? mediaActivity)
    {
        PluginLog.Verbose($"MediaActivityUpdated: {JsonConvert.SerializeObject(mediaActivity, Formatting.Indented)}");

        if (mediaActivity != null)
        {
            foreach (var activityConfig in Config.ActivityConfigs.Where(c => c.Enabled).OrderByDescending(c => c.Priority))
            {
                var matchesType =
                                 (mediaActivity.Type == ActivityType.Listening);
                PluginLog.Verbose($"Checking activity config '{activityConfig.Name}' for match: {matchesType}");
                if (matchesType)
                {
                    var matchFilter = true;
                    if (!activityConfig.FilterTemplate.IsNullOrWhitespace())
                    {
                        var filterTemplate = Template.Parse(activityConfig.FilterTemplate);
                        var filter = filterTemplate.Render(new { Activity = mediaActivity, Context = UpdaterContext }, member => member.Name);

                        if (bool.TryParse(filter, out var parsedFilter))
                        {
                            matchFilter = parsedFilter;
                        }
                        else
                        {
                            PluginLog.Error($"Unable to parse filter '{filter}' as boolean, skipping result");
                        }
                    }

                    if (matchFilter)
                    {
                        UpdaterContext.SecsElapsed = 0;
                        UpdateTitle = () =>
                        {
                            if (Config.Enabled && activityConfig.Enabled)
                            {
                                var titleTemplate = Template.Parse(activityConfig.TitleTemplate);
                                var title = titleTemplate.Render(new { Activity = mediaActivity, Context = UpdaterContext }, member => member.Name);

                                var data = new Dictionary<string, object>() {
                                    {"Title", title},
                                    {"IsPrefix", activityConfig.IsPrefix},
                                    {"Color", activityConfig.Color!},
                                    {"Glow", activityConfig.Glow!}
                                };

                                var serializedData = JsonConvert.SerializeObject(data, Formatting.Indented);
                                if (serializedData != UpdatedTitleJson)
                                {
                                    PluginLog.Verbose($"Call Honorific SetCharacterTitle IPC with:\n{serializedData}");
                                    SetCharacterTitleSubscriber.InvokeAction(0, serializedData);
                                    UpdatedTitleJson = serializedData;
                                }
                            }
                            else
                            {
                                ClearTitle();
                            }
                        };
                        return;
                    }
                }
            }
        }

        if (UpdateTitle != null || UpdatedTitleJson != null)
        {
            ClearTitle();
        }
    }

    private void OnFrameworkUpdate(IFramework framework)
    {
        if (Config.Enabled && UpdateTitle != null)
        {
            UpdateTitle.Invoke();
            UpdaterContext.SecsElapsed += framework.UpdateDelta.TotalSeconds;
        }
    }

    private void ClearTitle()
    {
        PluginLog.Verbose("Call Honorific ClearCharacterTitle IPC");
        Framework.RunOnFrameworkThread(() =>
        {
            ClearCharacterTitleSubscriber.InvokeAction(0);
        });
        UpdaterContext.SecsElapsed = 0;
        UpdateTitle = null;
        UpdatedTitleJson = null;
    }
}

public class MediaActivity
{
    public string Name { get; set; } = "";
    public string SongName { get; set; } = "";
    public string Artist { get; set; } = "";
    public ActivityType Type { get; set; }
}

public enum ActivityType
{
    Playing,
    Streaming,
    Listening,
    Watching,
    Custom,
    Competing
}
