using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

namespace IndieExchange
{
    [Serializable] internal sealed class PendingEvent
    {
        public string route, requestId, eventId, ticket;
        public long created;
        public long notBefore;
    }
    [Serializable] internal sealed class PendingEvents { public List<PendingEvent> items = new List<PendingEvent>(); }

    internal sealed class TrackingQueue
    {
        private readonly string file, origin, key;
        private readonly int timeout;
        private readonly CancellationToken token;
        private readonly IndieAdsSettings settings;
        private readonly string installation;
        private PendingEvents pending = new PendingEvents();
        private bool flushing;
        private double nextAttempt;
        private int failures;

        public TrackingQueue(string folder, IndieAdsSettings settings, CancellationToken token, string installation = "")
        {
            this.settings = settings;
            this.installation = installation;
            file = Path.Combine(folder, "events.json");
            origin = settings.apiBaseUrl;
            key = settings.sdkKey;
            timeout = settings.networkTimeoutSeconds;
            this.token = token;
            try { if (File.Exists(file)) pending = JsonUtility.FromJson<PendingEvents>(File.ReadAllText(file)) ?? new PendingEvents(); }
            catch { pending = new PendingEvents(); }
            if (pending.items == null) pending.items = new List<PendingEvent>();
            Prune();
        }

        public void Add(string route, string requestId, string ticket = null)
        {
            Prune();
            if (pending.items.Count >= 256) pending.items.RemoveAt(0);
            pending.items.Add(new PendingEvent { route = route, requestId = requestId, ticket = ticket,
                eventId = Guid.NewGuid().ToString("N"), created = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
                notBefore = DateTimeOffset.UtcNow.AddSeconds(1).ToUnixTimeMilliseconds() });
            Save();
        }

        private void Prune()
        {
            var oldest = DateTimeOffset.UtcNow.ToUnixTimeSeconds() - 86400;
            pending.items.RemoveAll(x => x == null || x.created < oldest ||
                (x.route != "impression" && x.route != "click") || string.IsNullOrEmpty(x.requestId) || string.IsNullOrEmpty(x.ticket));
            if (pending.items.Count > 256) pending.items.RemoveRange(0, pending.items.Count - 256);
        }

        public async Task Tick()
        {
            if (flushing || token.IsCancellationRequested || pending.items.Count == 0 || Time.realtimeSinceStartupAsDouble < nextAttempt) return;
            flushing = true;
            try
            {
                Prune();
                if (pending.items.Count == 0) return;
                var item = pending.items[0];
                if (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() < item.notBefore) return;
                var json = JsonUtility.ToJson(new TicketEvent { ticket = item.ticket });
                var result = await SecureTransport.Send(settings, installation, item.route, json, token);
                if (token.IsCancellationRequested) return;
                if (result.Ok || (result.status >= 400 && result.status < 500 && result.status != 408 && result.status != 409 && result.status != 429))
                {
                    pending.items.Remove(item);
                    failures = 0;
                    Save();
                    nextAttempt = Time.realtimeSinceStartupAsDouble + .25;
                }
                else
                {
                    failures = Math.Min(6, failures + 1);
                    nextAttempt = Time.realtimeSinceStartupAsDouble + Math.Min(60, Math.Pow(2, failures));
                }
            }
            finally { flushing = false; }
        }

        private void Save()
        {
            try
            {
                Directory.CreateDirectory(Path.GetDirectoryName(file));
                var temp = file + ".tmp";
                File.WriteAllText(temp, JsonUtility.ToJson(pending));
                if (File.Exists(file)) File.Replace(temp, file, null);
                else File.Move(temp, file);
            }
            catch { /* Tracking persistence is best effort; never hold up gameplay. */ }
        }
    }
}
