using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx.Unity.IL2CPP.Utils.Collections;
using HarmonyLib;
using UnityEngine;
using UnityEngine.Networking;

namespace BanMod
{
    [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.Update))]
    public static class BanModMessagePoller
    {
        private static float _nextMessagePollTime = 0f;
        private static float _nextReportPollTime = 0f;
        private static bool _pollRunning = false;
        private static bool _pollMessagesRequested = false;
        private static bool _pollReportsRequested = false;
        private const float FastReportPollIntervalSeconds = 4f;

        // Dopo OK/Chiudi, lo stesso identico messaggio non viene più mostrato
        // per l'intervallo scelto dal server: 1h, 3h, 5h, 9h, 12h o 24h.
        // Se titolo/testo/severity cambiano, la firma cambia e il popup viene mostrato subito.
        private const double DefaultMessageAckCooldownSeconds = 24d * 60d * 60d;
        private const string LocalAckPrefsPrefix = "BANMOD_COMM_MESSAGE_ACK_";
        private const string LocalReportReplyAckPrefsPrefix = "BANMOD_REPORT_REPLY_ACK_";

        private static readonly HashSet<string> _visibleMessageKeys = new HashSet<string>();
        private static readonly HashSet<string> _visibleReportReplyKeys = new HashSet<string>();

        public static void RequestImmediateReportPoll()
        {
            try { _nextReportPollTime = 0f; } catch { }
        }

        public static void RequestImmediatePoll()
        {
            try
            {
                _nextMessagePollTime = 0f;
                _nextReportPollTime = 0f;
            }
            catch { }
        }

        public static void Postfix()
        {
            try
            {
                // Messaggi generali restano nella sezione Comunicazioni e usano il polling normale.
                // Le risposte ai report invece vengono controllate più spesso, così il player online non aspetta 30 secondi.
                if (AmongUsClient.Instance == null)
                    return;

                bool pollMessages = Time.time >= _nextMessagePollTime;
                bool pollReports = Time.time >= _nextReportPollTime;

                if ((!pollMessages && !pollReports) || _pollRunning)
                    return;

                if (pollMessages)
                    _nextMessagePollTime = Time.time + BanModCommunicationConfig.MessagePollIntervalSeconds;

                if (pollReports)
                    _nextReportPollTime = Time.time + FastReportPollIntervalSeconds;

                _pollMessagesRequested = pollMessages;
                _pollReportsRequested = pollReports;
                AmongUsClient.Instance.StartCoroutine(PollWrapper().WrapToIl2Cpp());
            }
            catch { }
        }

        private static IEnumerator PollWrapper()
        {
            _pollRunning = true;

            bool pollMessages = _pollMessagesRequested;
            bool pollReports = _pollReportsRequested;
            _pollMessagesRequested = false;
            _pollReportsRequested = false;

            yield return PollServerMessages(pollMessages, pollReports);
            _pollRunning = false;
        }

        private static IEnumerator PollServerMessages(bool pollMessages, bool pollReports)
        {
            if (!pollMessages && !pollReports)
                yield break;

            if (!pollMessages)
            {
                if (pollReports)
                    yield return PollReportReplies();
                yield break;
            }

            bool hasToken = false;

            yield return BanModApiTokenManager.EnsureTokenCoroutine((success, token) =>
            {
                hasToken = success;
            });

            if (!hasToken)
                yield break;

            UnityWebRequest request = UnityWebRequest.Get(BanModCommunicationConfig.MessagesUrl);
            request.downloadHandler = new DownloadHandlerBuffer();
            request.timeout = 30;
            BanModApiTokenManager.ApplyAuthHeader(request);

            yield return request.SendWebRequest();

            if (request.responseCode == 401)
            {
                BanModApiTokenManager.ClearToken();
                request.Dispose();
                yield break;
            }

            if (request.result != UnityWebRequest.Result.Success)
            {
                request.Dispose();
                yield break;
            }

            string responseText = request.downloadHandler != null ? request.downloadHandler.text : "";
            request.Dispose();

            List<ServerMessage> messages = ParseMessages(responseText);

            // Mostriamo un solo popup alla volta. Se ce ne sono altri, arrivano al polling successivo.
            foreach (ServerMessage msg in messages)
            {
                if (msg == null || msg.Id <= 0)
                    continue;

                if (!ShouldShowMessage(msg))
                    continue;

                ShowServerMessagePopup(msg);
                yield break;
            }

            if (pollReports)
                yield return PollReportReplies();
        }

        private static IEnumerator PollReportReplies()
        {
            List<BanModCommunicationManager.ReportSummary> reports = null;
            string error = "";

            yield return BanModCommunicationManager.GetMyReportsCoroutine((items, err) =>
            {
                reports = items;
                error = err ?? "";
            });

            if (!string.IsNullOrWhiteSpace(error) || reports == null || reports.Count <= 0)
                yield break;

            for (int i = 0; i < reports.Count; i++)
            {
                BanModCommunicationManager.ReportSummary report = reports[i];
                if (report == null || report.Id <= 0 || report.DeletedByPlayer)
                    continue;

                BanModCommunicationManager.ReportChatMessage adminMessage = GetLatestAdminReportMessage(report);
                if (adminMessage == null || string.IsNullOrWhiteSpace(adminMessage.Message))
                    continue;

                string signature = BuildReportReplySignature(report, adminMessage);
                if (string.IsNullOrWhiteSpace(signature))
                    continue;

                string visibleKey = report.Id.ToString() + "_" + signature;
                if (_visibleReportReplyKeys.Contains(visibleKey))
                    continue;

                string lastSeenSignature = GetLocalReportReplyAck(report.Id);
                if (!string.IsNullOrWhiteSpace(lastSeenSignature) && lastSeenSignature == signature)
                    continue;

                ShowReportReplyPopup(report, signature, visibleKey);
                yield break;
            }
        }

        private static bool ShouldShowMessage(ServerMessage msg)
        {
            if (msg == null || msg.Id <= 0)
                return false;

            string key = GetMessageAckKey(msg);
            if (string.IsNullOrWhiteSpace(key))
                return false;

            if (_visibleMessageKeys.Contains(key))
                return false;

            double lastAck = GetLocalAckUnixSeconds(key);
            double cooldownSeconds = GetReminderCooldownSeconds(msg);
            if (lastAck > 0 && UnixNowSeconds() - lastAck < cooldownSeconds)
                return false;

            return true;
        }

        private static void AcknowledgeMessage(ServerMessage msg)
        {
            if (msg == null || msg.Id <= 0)
                return;

            string key = GetMessageAckKey(msg);
            if (!string.IsNullOrWhiteSpace(key))
            {
                SaveLocalAck(key);
                _visibleMessageKeys.Remove(key);
            }

            try
            {
                if (AmongUsClient.Instance != null)
                    AmongUsClient.Instance.StartCoroutine(MarkMessageRead(msg.Id, msg.Signature).WrapToIl2Cpp());
            }
            catch { }
        }

        private static IEnumerator MarkMessageRead(int messageId, string signature)
        {
            string json = "{\"signature\":" + BanModJson.StringValue(signature ?? "") + "}";

            UnityWebRequest request = new UnityWebRequest(BanModCommunicationConfig.MessageReadUrl(messageId), "POST");
            request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(json));
            request.downloadHandler = new DownloadHandlerBuffer();
            request.SetRequestHeader("Content-Type", "application/json");
            request.timeout = 15;
            BanModApiTokenManager.ApplyAuthHeader(request);

            yield return request.SendWebRequest();
            request.Dispose();
        }

        private static void ShowServerMessagePopup(ServerMessage msg)
        {
            string title = string.IsNullOrWhiteSpace(msg.Title) ? "Messaggio BANMOD" : msg.Title.Trim();
            string body = string.IsNullOrWhiteSpace(msg.Message) ? "" : msg.Message.Trim();
            string key = GetMessageAckKey(msg);

            if (!string.IsNullOrWhiteSpace(key))
                _visibleMessageKeys.Add(key);

            try
            {
                BanModPopup.CreateMessagePopup(title, body, () => AcknowledgeMessage(msg));
                return;
            }
            catch
            {
                if (!string.IsNullOrWhiteSpace(key))
                    _visibleMessageKeys.Remove(key);
            }

            try
            {
                string text = "[BANMOD] " + title;
                if (!string.IsNullOrWhiteSpace(body))
                    text += "\n" + body;

                Debug.LogWarning(text);
            }
            catch { }
        }

        private static BanModCommunicationManager.ReportChatMessage GetLatestAdminReportMessage(BanModCommunicationManager.ReportSummary report)
        {
            if (report == null || report.Chat == null)
                return null;

            BanModCommunicationManager.ReportChatMessage latest = null;
            for (int i = 0; i < report.Chat.Count; i++)
            {
                BanModCommunicationManager.ReportChatMessage msg = report.Chat[i];
                if (msg == null || !string.Equals(msg.AuthorType ?? "", "admin", StringComparison.OrdinalIgnoreCase))
                    continue;

                if (latest == null || msg.CreatedAt > latest.CreatedAt || msg.Id > latest.Id)
                    latest = msg;
            }

            return latest;
        }

        private static string BuildReportReplySignature(BanModCommunicationManager.ReportSummary report, BanModCommunicationManager.ReportChatMessage msg)
        {
            if (report == null || msg == null)
                return "";

            string raw = report.Id.ToString() + "|"
                + msg.Id.ToString() + "|"
                + msg.CreatedAt.ToString(System.Globalization.CultureInfo.InvariantCulture) + "|"
                + (msg.AuthorName ?? "") + "|"
                + (msg.Message ?? "");

            return StableHash(raw);
        }

        private static void ShowReportReplyPopup(BanModCommunicationManager.ReportSummary report, string signature, string visibleKey)
        {
            if (report == null || report.Id <= 0)
                return;

            if (!string.IsNullOrWhiteSpace(visibleKey))
                _visibleReportReplyKeys.Add(visibleKey);

            try
            {
                BanModCommunicationUi.EnsureCreated();

                if (BanModCommunicationUi.Instance != null)
                {
                    BanModCommunicationUi.Instance.ShowReportChatPopup(report, () => AcknowledgeReportReply(report.Id, signature, visibleKey));
                    return;
                }
            }
            catch
            {
                if (!string.IsNullOrWhiteSpace(visibleKey))
                    _visibleReportReplyKeys.Remove(visibleKey);
            }

            try
            {
                SaveLocalReportReplyAck(report.Id, signature);
                Debug.LogWarning("[BANMOD REPORT] #" + report.Id + " " + (report.Title ?? "") + "\n" + (report.AdminReply ?? ""));
            }
            catch { }
        }

        private static void AcknowledgeReportReply(int reportId, string signature, string visibleKey)
        {
            SaveLocalReportReplyAck(reportId, signature);

            if (!string.IsNullOrWhiteSpace(visibleKey))
                _visibleReportReplyKeys.Remove(visibleKey);
        }

        private static void SaveLocalReportReplyAck(int reportId, string signature)
        {
            try
            {
                if (reportId <= 0 || string.IsNullOrWhiteSpace(signature))
                    return;

                PlayerPrefs.SetString(LocalReportReplyAckPrefsPrefix + reportId.ToString(), signature);
                PlayerPrefs.Save();
            }
            catch { }
        }

        private static string GetLocalReportReplyAck(int reportId)
        {
            try
            {
                if (reportId <= 0)
                    return "";

                return PlayerPrefs.GetString(LocalReportReplyAckPrefsPrefix + reportId.ToString(), "");
            }
            catch
            {
                return "";
            }
        }

        private static string GetMessageAckKey(ServerMessage msg)
        {
            if (msg == null || msg.Id <= 0)
                return "";

            string signature = msg.Signature;
            if (string.IsNullOrWhiteSpace(signature))
                signature = BuildLocalSignature(msg);

            return msg.Id.ToString() + "_" + signature;
        }

        private static string BuildLocalSignature(ServerMessage msg)
        {
            string raw = (msg.Id.ToString() + "|" + (msg.Title ?? "") + "|" + (msg.Message ?? "") + "|" + (msg.Severity ?? "") + "|" + msg.ReminderHours.ToString()).Trim();
            return StableHash(raw);
        }

        private static double GetReminderCooldownSeconds(ServerMessage msg)
        {
            int hours = msg != null ? msg.ReminderHours : 24;

            if (hours != 1 && hours != 3 && hours != 5 && hours != 9 && hours != 12 && hours != 24)
                hours = 24;

            double seconds = hours * 60d * 60d;
            return seconds > 0 ? seconds : DefaultMessageAckCooldownSeconds;
        }

        private static string StableHash(string value)
        {
            try
            {
                unchecked
                {
                    uint hash = 2166136261;
                    string text = value ?? "";

                    for (int i = 0; i < text.Length; i++)
                    {
                        hash ^= text[i];
                        hash *= 16777619;
                    }

                    return hash.ToString("x8");
                }
            }
            catch
            {
                return "fallback";
            }
        }

        private static void SaveLocalAck(string key)
        {
            try
            {
                PlayerPrefs.SetString(LocalAckPrefsPrefix + key, UnixNowSeconds().ToString(System.Globalization.CultureInfo.InvariantCulture));
                PlayerPrefs.Save();
            }
            catch { }
        }

        private static double GetLocalAckUnixSeconds(string key)
        {
            try
            {
                string raw = PlayerPrefs.GetString(LocalAckPrefsPrefix + key, "");
                if (double.TryParse(raw, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double value))
                    return value;
            }
            catch { }

            return 0;
        }

        private static double UnixNowSeconds()
        {
            try
            {
                return (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
            }
            catch
            {
                return 0;
            }
        }

        private static List<ServerMessage> ParseMessages(string json)
        {
            List<ServerMessage> result = new List<ServerMessage>();

            if (string.IsNullOrWhiteSpace(json))
                return result;

            try
            {
                MatchCollection objects = Regex.Matches(json, "\\{[^\\{\\}]*\\\"id\\\"[^\\{\\}]*\\}");

                foreach (Match match in objects)
                {
                    string obj = match.Value;
                    int id = ExtractJsonInt(obj, "id", 0);

                    if (id <= 0)
                        continue;

                    ServerMessage msg = new ServerMessage
                    {
                        Id = id,
                        Title = BanModApiTokenManager.ExtractJsonString(obj, "title", ""),
                        Message = BanModApiTokenManager.ExtractJsonString(obj, "message", ""),
                        Severity = BanModApiTokenManager.ExtractJsonString(obj, "severity", "info"),
                        Signature = BanModApiTokenManager.ExtractJsonString(obj, "signature", ""),
                        ReminderHours = ExtractJsonInt(obj, "reminder_hours", ExtractJsonInt(obj, "reminderHours", 24))
                    };

                    if (string.IsNullOrWhiteSpace(msg.Signature))
                        msg.Signature = BuildLocalSignature(msg);

                    result.Add(msg);
                }
            }
            catch { }

            return result;
        }

        private static int ExtractJsonInt(string json, string key, int fallback)
        {
            if (string.IsNullOrWhiteSpace(json))
                return fallback;

            string search = "\"" + key + "\"";
            int keyIndex = json.IndexOf(search, StringComparison.OrdinalIgnoreCase);
            if (keyIndex < 0)
                return fallback;

            int colonIndex = json.IndexOf(':', keyIndex);
            if (colonIndex < 0)
                return fallback;

            int start = colonIndex + 1;
            while (start < json.Length && char.IsWhiteSpace(json[start]))
                start++;

            int end = start;
            while (end < json.Length && (char.IsDigit(json[end]) || json[end] == '-'))
                end++;

            if (int.TryParse(json.Substring(start, end - start), out int value))
                return value;

            return fallback;
        }

        private class ServerMessage
        {
            public int Id;
            public string Title;
            public string Message;
            public string Severity;
            public string Signature;
            public int ReminderHours;
        }
    }
}
