﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using UnityEngine;

namespace IndieCrab
{
    public class IndieCrabLogger : MonoBehaviour
    {
        private const float RetryIntervalSeconds = 30f;
        private const int MaxContextFieldLength = 256;
        [SerializeField] private IndieCrabConfiguration configuration;

        private const int MaxMessageLength = 1024;
        private const int MaxStackTraceLength = 16384;

        private static IndieCrabLogger Instance;
        private static readonly object ContextLock = new object();

        private readonly ConcurrentQueue<IndieCrabMessage> Queue = new ConcurrentQueue<IndieCrabMessage>();
        private readonly WaitForSeconds retryWait = new WaitForSeconds(RetryIntervalSeconds);

        private string apiKey;
        private string environment;
        private int maxQueuedMessages = 128;
        private bool sendInProgress;
        private bool retryScheduled;
        private string contextUserId;
        private string contextUserName;
        private string contextUserState;
        private string contextActiveScene;
        private string contextActivePanel;

        public static void SetContextUserId(string value)
        {
            if (Instance == null) return;
            Instance.SetContextValue(ref Instance.contextUserId, value);
        }

        public static void ClearContextUserId()
        {
            if (Instance == null) return;
            Instance.SetContextValue(ref Instance.contextUserId, null);
        }

        public static void SetContextUserName(string value)
        {
            if (Instance == null) return;
            Instance.SetContextValue(ref Instance.contextUserName, value);
        }

        public static void ClearContextUserName()
        {
            if (Instance == null) return;
            Instance.SetContextValue(ref Instance.contextUserName, null);
        }

        public static void SetContextUserState(string value)
        {
            if (Instance == null) return;
            Instance.SetContextValue(ref Instance.contextUserState, value);
        }

        public static void ClearContextUserState()
        {
            if (Instance == null) return;
            Instance.SetContextValue(ref Instance.contextUserState, null);
        }

        public static void SetContextActiveScene(string value)
        {
            if (Instance == null) return;
            Instance.SetContextValue(ref Instance.contextActiveScene, value);
        }

        public static void ClearContextActiveScene()
        {
            if (Instance == null) return;
            Instance.SetContextValue(ref Instance.contextActiveScene, null);
        }

        public static void SetContextActivePanel(string value)
        {
            if (Instance == null) return;
            Instance.SetContextValue(ref Instance.contextActivePanel, value);
        }

        public static void ClearContextActivePanel()
        {
            if (Instance == null) return;
            Instance.SetContextValue(ref Instance.contextActivePanel, null);
        }

        private void Awake()
        {
            if (Instance != null && Instance != this)
            {
                // Debug.Log("[IndieCrab] Duplicate logger instance detected, destroying duplicate.");
                Destroy(gameObject);
                return;
            }

            Instance = this;
            DontDestroyOnLoad(gameObject);
            CacheConfiguration();
            // Debug.Log($"[IndieCrab] Logger awake. Endpoint={LogEndpoint}, Environment={environment}, ApiKeyConfigured={!string.IsNullOrWhiteSpace(apiKey)}, ReportError={reportError}, ReportException={reportException}, MaxQueue={maxQueuedMessages}");
            
            Application.SetStackTraceLogType(LogType.Error, StackTraceLogType.ScriptOnly);
            Application.SetStackTraceLogType(LogType.Exception, StackTraceLogType.ScriptOnly);
            Application.logMessageReceivedThreaded += HandleLog;

            StartCoroutine(SendLoop());
        }

        private void OnDestroy()
        {
            if (Instance == this)
            {
                Application.logMessageReceivedThreaded -= HandleLog;
                Instance = null;
            }
        }

        private void HandleLog(string message, string stackTrace, LogType type)
        {
            if (configuration == null)
            {
                return;
            }

            if (Application.isEditor && !configuration.AllowLoggingFromEditor)
            {
                return;
            }

            if (!configuration.IsValidLogType(type))
            {
                return;
            }

            while (Queue.Count >= maxQueuedMessages && Queue.TryDequeue(out _))
            {
                // Debug.Log("[IndieCrab] Queue full, dropped oldest log entry.");
            }

            IndieCrabMessage contextSnapshot = CreateContextSnapshot();

            Queue.Enqueue(new IndieCrabMessage
            {
                timestampUtc = DateTime.UtcNow.ToString("o"),
                version = Application.version,
                type = type.ToString(),
                message = Truncate(message, MaxMessageLength),
                stackTrace = Truncate(stackTrace, MaxStackTraceLength),
                environment = environment,
                userId = contextSnapshot.userId,
                userName = contextSnapshot.userName,
                userState = contextSnapshot.userState,
                activeScene = contextSnapshot.activeScene,
                activePanel = contextSnapshot.activePanel
            });

            // Debug.Log($"[IndieCrab] Queued {type}. QueueCount={Queue.Count}, Message={Truncate(message, 200)}");
        }

        private IEnumerator SendLoop()
        {
            while (true)
            {
                if (sendInProgress || retryScheduled || string.IsNullOrWhiteSpace(apiKey))
                {
                    if (string.IsNullOrWhiteSpace(apiKey))
                    {
                        // Debug.Log("[IndieCrab] Send loop waiting because no API key is configured.");
                    }
                    yield return null;
                    continue;
                }

                if (!Queue.TryPeek(out IndieCrabMessage entry))
                {
                    yield return null;
                    continue;
                }

                Send(entry);
                yield return null;
            }
        }

        private void Send(IndieCrabMessage entry)
        {
            sendInProgress = true;
            // Debug.Log($"[IndieCrab] Sending log to {LogEndpoint}. Type={entry.type}, Timestamp={entry.timestampUtc}, QueueCount={Queue.Count}");

            IndieCrabRequest request = IndieCrabRequest.Instantiate();
            request.AddField("api_key", apiKey);
            request.AddField("timestamp_utc", entry.timestampUtc);
            request.AddField("version", entry.version);
            request.AddField("type", entry.type);
            request.AddField("message", entry.message);
            request.AddField("stacktrace", entry.stackTrace);
            request.AddField("environment", entry.environment);
            request.AddOptionalField("user_id", entry.userId);
            request.AddOptionalField("user_name", entry.userName);
            request.AddOptionalField("user_state", entry.userState);
            request.AddOptionalField("active_scene", entry.activeScene);
            request.AddOptionalField("active_panel", entry.activePanel);
            request.Send(OnSendCompleted);
        }

        private void OnSendCompleted(IndieCrabRequest.CompletionState completionState)
        {
            sendInProgress = false;
            // Debug.Log($"[IndieCrab] Send completed. State={completionState}, QueueCountBeforeHandling={Queue.Count}");

            if (completionState == IndieCrabRequest.CompletionState.Success)
            {
                Queue.TryDequeue(out _);
                // Debug.Log($"[IndieCrab] Removed sent entry from queue. QueueCountNow={Queue.Count}");
                return;
            }

            if (completionState == IndieCrabRequest.CompletionState.PermanentFailure)
            {
                Queue.TryDequeue(out _);
                // Debug.Log($"[IndieCrab] Dropped log entry after permanent failure. QueueCountNow={Queue.Count}");
                return;
            }

            if (!retryScheduled && isActiveAndEnabled)
            {
                // Debug.Log($"[IndieCrab] Scheduling retry in {RetryIntervalSeconds} seconds.");
                StartCoroutine(ScheduleRetry());
            }
        }

        private IEnumerator ScheduleRetry()
        {
            retryScheduled = true;
            yield return retryWait;
            retryScheduled = false;
        }

        private void CacheConfiguration()
        {
            environment = GetEnvironmentName(Application.platform);

            if (configuration == null)
            {
                // Debug.Log("[IndieCrab] No configuration asset assigned.");
                return;
            }

            apiKey = configuration.ApiKey;
            maxQueuedMessages = configuration.MaxQueuedMessages;
            // Debug.Log($"[IndieCrab] Configuration loaded. ApiKeyConfigured={!string.IsNullOrWhiteSpace(apiKey)}, MaxQueue={maxQueuedMessages}");
        }

        private IndieCrabMessage CreateContextSnapshot()
        {
            lock (ContextLock)
            {
                return new IndieCrabMessage
                {
                    userId = contextUserId,
                    userName = contextUserName,
                    userState = contextUserState,
                    activeScene = contextActiveScene,
                    activePanel = contextActivePanel
                };
            }
        }

        private void SetContextValue(ref string target, string value)
        {
            lock (ContextLock)
            {
                target = NormalizeContextValue(value);
            }
        }

        private static string GetEnvironmentName(RuntimePlatform platform)
        {
            switch (platform)
            {
                case RuntimePlatform.OSXEditor:
                case RuntimePlatform.OSXPlayer:
                    return "macos";
                case RuntimePlatform.IPhonePlayer:
                    return "ios";
                case RuntimePlatform.Android:
                    return "android";
                default:
                    return "windows";
            }
        }

        private static string Truncate(string value, int maxLength)
        {
            if (string.IsNullOrEmpty(value) || value.Length <= maxLength) return value;
            return value.Substring(0, maxLength);
        }

        private static string NormalizeContextValue(string value)
        {
            if (string.IsNullOrWhiteSpace(value)) return null;
            return Truncate(value, MaxContextFieldLength);
        }
    }
}
