using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

namespace PolyTraderSharp.Services
{
    public class PolymarketApiService
    {
        private readonly TerminalLogger _logger;
        private readonly HttpClient _httpClient;
        
        private readonly string _dataHost = "https://data-api.polymarket.com";
        private readonly string _clobHost = "https://clob.polymarket.com";

        // Per-endpoint rate tracking (10-second windows matching Polymarket limits)
        // Data API has per-endpoint limits that are stricter than the general 1000/10s
        private readonly ConcurrentQueue<DateTime> _dataActivityTimestamps = new();   // /activity → General 1000/10s
        private readonly ConcurrentQueue<DateTime> _dataPositionsTimestamps = new();  // /positions → 150/10s
        private readonly ConcurrentQueue<DateTime> _gammaApiTimestamps = new();       // /events → 500/10s
        private readonly ConcurrentQueue<DateTime> _clobApiTimestamps = new();        // General 9000/10s
        private long _lastPingMs = 0;

        // Polymarket documented rate limits per 10 seconds (per endpoint we use)
        public static readonly Dictionary<string, int> RateLimits = new()
        {
            { "Activity", 1000 },    // Data API /activity (General limit, no specific)
            { "Positions", 150 },    // Data API /positions (specific endpoint limit!)
            { "Gamma", 500 },        // Gamma API /events (specific endpoint limit)
            { "CLOB", 9000 }         // CLOB API General
        };

        public PolymarketApiService(TerminalLogger logger, HttpClient httpClient)
        {
            _logger = logger;
            _httpClient = httpClient;
            _httpClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
            _httpClient.Timeout = TimeSpan.FromSeconds(30);
        }

        public long GetLastPing() => _lastPingMs;

        /// <summary>
        /// Returns per-endpoint request counts in the last 10 seconds.
        /// Keys match the RateLimits dictionary.
        /// </summary>
        public Dictionary<string, int> GetRateLimitsPerTenSeconds()
        {
            var cutoff = DateTime.UtcNow.AddSeconds(-10);
            return new Dictionary<string, int>
            {
                { "Activity", CountRecent(_dataActivityTimestamps, cutoff) },
                { "Positions", CountRecent(_dataPositionsTimestamps, cutoff) },
                { "Gamma", CountRecent(_gammaApiTimestamps, cutoff) },
                { "CLOB", CountRecent(_clobApiTimestamps, cutoff) }
            };
        }

        private static int CountRecent(ConcurrentQueue<DateTime> queue, DateTime cutoff)
        {
            int count = 0;
            foreach (var dt in queue.ToArray())
            {
                if (dt >= cutoff) count++;
            }
            return count;
        }

        private void TrackRequest(string apiType)
        {
            var queue = apiType switch
            {
                "Activity" => _dataActivityTimestamps,
                "Positions" => _dataPositionsTimestamps,
                "Gamma" => _gammaApiTimestamps,
                "CLOB" => _clobApiTimestamps,
                _ => _clobApiTimestamps
            };
            queue.Enqueue(DateTime.UtcNow);
            while (queue.TryPeek(out DateTime oldest) && oldest < DateTime.UtcNow.AddSeconds(-10))
            {
                queue.TryDequeue(out _);
            }
        }

        public async Task<int> MeasurePingAsync()
        {
            try
            {
                TrackRequest("CLOB");
                var sw = Stopwatch.StartNew();
                using var response = await _httpClient.GetAsync($"{_clobHost}/time");
                sw.Stop();
                if (response.IsSuccessStatusCode)
                {
                    _lastPingMs = sw.ElapsedMilliseconds;
                    return (int)_lastPingMs;
                }
            }
            catch { }
            return -1;
        }

        private async Task<HttpResponseMessage> GetWithRetryAsync(string url)
        {
            int maxRetries = 3;
            for (int i = 0; i < maxRetries; i++)
            {
                try
                {
                    var response = await _httpClient.GetAsync(url);
                    if ((int)response.StatusCode == 429) // Rate limit
                    {
                        var delay = Math.Pow(2, i + 1);
                        _logger.Warning($"API Rate-Limit (429) auf {url}. Retry in {delay}s...");
                        await Task.Delay(TimeSpan.FromSeconds(delay));
                        continue;
                    }
                    return response;
                }
                catch (TaskCanceledException)
                {
                    if (i == maxRetries - 1) throw;
                    var delay = Math.Pow(2, i + 1);
                    _logger.Warning($"API Timeout auf {url}. Retry in {delay}s...");
                    await Task.Delay(TimeSpan.FromSeconds(delay));
                }
                catch (HttpRequestException)
                {
                    if (i == maxRetries - 1) throw;
                    var delay = Math.Pow(2, i + 1);
                    _logger.Warning($"Netzwerkfehler auf {url}. Retry in {delay}s...");
                    await Task.Delay(TimeSpan.FromSeconds(delay));
                }
            }
            return await _httpClient.GetAsync(url); //Fallback
        }

        /// <summary>
        /// Fetches the recent trading activity for a given wallet address.
        /// </summary>
        public async Task<List<JsonElement>> GetTraderActivityAsync(string walletAddress, int limit = 50)
        {
            TrackRequest("Activity");
            try
            {
                long cb = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                string url = $"{_dataHost}/activity?limit={limit}&user={walletAddress}&type=TRADE&_cb={cb}";
                using var response = await GetWithRetryAsync(url);
                
                if (!response.IsSuccessStatusCode)
                {
                    _logger.Warning($"API returned {response.StatusCode} for {walletAddress}");
                    return new List<JsonElement>();
                }
                
                var jsonStr = await response.Content.ReadAsStringAsync();
                using var document = JsonDocument.Parse(jsonStr);
                
                var list = new List<JsonElement>();
                if (document.RootElement.ValueKind == JsonValueKind.Array)
                {
                    foreach (var element in document.RootElement.EnumerateArray())
                    {
                        list.Add(element.Clone());
                    }
                }
                return list;
            }
            catch (Exception ex)
            {
                _logger.Error($"Failed to fetch activity for {walletAddress}: {ex.Message}");
                return new List<JsonElement>();
            }
        }
        
        /// <summary>
        /// Fetches the current best price from the CLOB orderbook for a given token.
        /// For SELL: returns the best bid (highest buy offer).
        /// For BUY: returns the best ask (lowest sell offer).
        /// </summary>
        public async Task<decimal?> GetOrderBookPriceAsync(string tokenId, string side = "SELL")
        {
            TrackRequest("CLOB");
            try
            {
                string url = $"{_clobHost}/book?token_id={tokenId}";
                using var response = await GetWithRetryAsync(url);
                if (!response.IsSuccessStatusCode)
                {
                    _logger.Warning($"Orderbook request failed: {response.StatusCode}");
                    return null;
                }

                var jsonStr = await response.Content.ReadAsStringAsync();
                using var doc = JsonDocument.Parse(jsonStr);

                // For SELL we want the best bid (buyer's highest price)
                // For BUY we want the best ask (seller's lowest price)
                string bookSide = side.ToUpper() == "SELL" ? "bids" : "asks";

                if (doc.RootElement.TryGetProperty(bookSide, out var orders) && 
                    orders.ValueKind == JsonValueKind.Array && orders.GetArrayLength() > 0)
                {
                    var priceList = new List<decimal>();
                    foreach (var order in orders.EnumerateArray())
                    {
                        if (order.TryGetProperty("price", out var priceProp))
                        {
                            string priceStr = priceProp.GetString() ?? "";
                            if (decimal.TryParse(priceStr, System.Globalization.NumberStyles.Any, 
                                System.Globalization.CultureInfo.InvariantCulture, out decimal p))
                            {
                                priceList.Add(p);
                            }
                        }
                    }

                    if (priceList.Count > 0)
                    {
                        // Seller wants the highest bid. Buyer wants the lowest ask.
                        if (side.ToUpper() == "SELL") return priceList.Max();
                        else return priceList.Min();
                    }
                }

                _logger.Warning($"Orderbook leer oder kein Preis gefunden für Token {tokenId}");
                return null;
            }
            catch (Exception ex)
            {
                _logger.Error($"GetOrderBookPriceAsync Fehler: {ex.Message}");
                return null;
            }
        }

        public async Task<decimal> GetUsdcBalanceAsync(string walletAddress)
        {
            if (string.IsNullOrEmpty(walletAddress)) return 0;
            decimal totalBalance = 0;
            try
            {
                string addressObj = walletAddress.Replace("0x", "").PadLeft(64, '0');
                string data = "0x70a08231" + addressObj;

                string[] rpcs = { "https://polygon-rpc.com", "https://polygon.llamarpc.com", "https://rpc.ankr.com/polygon" };
                string[] contracts = { "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" };

                foreach (var usdcContract in contracts)
                {
                    bool success = false;
                    foreach (var rpcUrl in rpcs)
                    {
                        var payload = new
                        {
                            jsonrpc = "2.0",
                            method = "eth_call",
                            @params = new object[]
                            {
                                new { to = usdcContract, data },
                                "latest"
                            },
                            id = 1
                        };

                        try
                        {
                            var content = new StringContent(JsonSerializer.Serialize(payload), System.Text.Encoding.UTF8, "application/json");
                            using var response = await _httpClient.PostAsync(rpcUrl, content);
                            if (response.IsSuccessStatusCode)
                            {
                                var json = await response.Content.ReadAsStringAsync();
                                using var doc = JsonDocument.Parse(json);
                                if (doc.RootElement.TryGetProperty("result", out var res) && res.ValueKind == JsonValueKind.String)
                                {
                                    string hexBal = res.GetString() ?? "0x0";
                                    if (hexBal.StartsWith("0x")) hexBal = hexBal.Substring(2);
                                    if (!string.IsNullOrEmpty(hexBal))
                                    {
                                        long rawBalance = Convert.ToInt64(hexBal, 16);
                                        decimal pVal = (decimal)rawBalance / 1_000_000m;
                                        totalBalance += pVal;
                                        if (pVal > 0) _logger.Info($"🌐 [{walletAddress.Substring(0, 6)}...] Balance gefunden: ${pVal:F2} auf Contract {usdcContract}");
                                    }
                                }
                                success = true;
                                break;
                            }
                        }
                        catch (Exception exInner) { _logger.Error($"USDC Balance RPC Exception on {rpcUrl}: {exInner.Message}"); }
                    }
                    if (!success) _logger.Warning($"Fehler beim Abruf von USDC Token {usdcContract}");
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"USDC Balance fetch failed for {walletAddress}: {ex.Message}");
            }
            return totalBalance;
        }

        public async Task<(bool isResolved, bool isWinner)> CheckMarketResolutionAsync(string slug, string tokenId)
        {
            if (string.IsNullOrEmpty(tokenId)) return (false, false);
            try
            {
                TrackRequest("Gamma");
                using var response = await GetWithRetryAsync($"https://gamma-api.polymarket.com/markets?clob_token_ids={tokenId}");
                if (response.IsSuccessStatusCode)
                {
                    var jsonStr = await response.Content.ReadAsStringAsync();
                    using var doc = JsonDocument.Parse(jsonStr);
                    if (doc.RootElement.ValueKind == JsonValueKind.Array && doc.RootElement.GetArrayLength() > 0)
                    {
                        var mkt = doc.RootElement[0];
                        
                        bool mktClosed = mkt.TryGetProperty("closed", out var mc) && mc.GetBoolean();
                        if (!mktClosed) return (false, false);

                        if (mkt.TryGetProperty("clobTokenIds", out var cIdsStr) && mkt.TryGetProperty("outcomePrices", out var pricesStr))
                        {
                            using var cDoc = JsonDocument.Parse(cIdsStr.GetString() ?? "[]");
                            using var pDoc = JsonDocument.Parse(pricesStr.GetString() ?? "[]");
                            
                            var ids = cDoc.RootElement.EnumerateArray().ToList();
                            var prices = pDoc.RootElement.EnumerateArray().ToList();
                            
                            for (int i = 0; i < ids.Count; i++)
                            {
                                if (ids[i].GetString() == tokenId)
                                {
                                    if (i < prices.Count)
                                    {
                                        if (decimal.TryParse(prices[i].GetString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var px) && px >= 0.99m)
                                            return (true, true);
                                        else
                                            return (true, false);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"Error checking market resolution for token {tokenId}: {ex.Message}");
            }
            return (false, false);
        }

        public async Task<List<JsonElement>?> SyncOpenPositionsAsync(string walletAddress)
        {
            if (string.IsNullOrEmpty(walletAddress)) return new List<JsonElement>();
            try
            {
                var allPositions = new List<JsonElement>();
                int limit = 500;
                int offset = 0;

                while (true)
                {
                    TrackRequest("Positions");
                    using var response = await GetWithRetryAsync($"https://data-api.polymarket.com/positions?user={walletAddress}&limit={limit}&offset={offset}");
                    if (response.IsSuccessStatusCode)
                    {
                        var json = await response.Content.ReadAsStringAsync();
                        using var doc = JsonDocument.Parse(json);
                        
                        if (doc.RootElement.ValueKind == JsonValueKind.Array)
                        {
                            int count = 0;
                            foreach (var el in doc.RootElement.EnumerateArray())
                            {
                                allPositions.Add(el.Clone());
                                count++;
                            }
                            if (count < limit) break; // Reached the end
                            offset += limit;
                        }
                        else
                        {
                            break;
                        }
                    }
                    else
                    {
                        _logger.Error($"Failed to fetch open positions (HTTP {(int)response.StatusCode}): {response.ReasonPhrase}");
                        break;
                    }
                }
                return allPositions.Count > 0 ? allPositions : null;
            }
            catch (Exception ex)
            {
                _logger.Error($"Failed to fetch open positions for {walletAddress}: {ex.Message}");
            }
            return null;
        }

        /// <summary>
        /// Fetches the current position sizes a master trader holds for a set of token IDs.
        /// Returns a Dictionary mapping TokenId -> Shares held. Only includes tokens with size > 0.
        /// </summary>
        public async Task<Dictionary<string, decimal>> GetTraderPositionSizesAsync(string walletAddress, HashSet<string> relevantTokenIds)
        {
            var result = new Dictionary<string, decimal>();
            if (string.IsNullOrEmpty(walletAddress) || relevantTokenIds.Count == 0) return result;

            try
            {
                int limit = 500;
                int offset = 0;

                while (true)
                {
                    TrackRequest("Positions");
                    using var response = await GetWithRetryAsync($"https://data-api.polymarket.com/positions?user={walletAddress}&limit={limit}&offset={offset}&sizeThreshold=0.1");
                    if (!response.IsSuccessStatusCode) break;

                    var json = await response.Content.ReadAsStringAsync();
                    using var doc = JsonDocument.Parse(json);
                    if (doc.RootElement.ValueKind != JsonValueKind.Array) break;

                    int count = 0;
                    foreach (var el in doc.RootElement.EnumerateArray())
                    {
                        count++;
                        string asset = el.TryGetProperty("asset", out var ap) ? ap.GetString() ?? "" : "";
                        if (!string.IsNullOrEmpty(asset) && relevantTokenIds.Contains(asset))
                        {
                            decimal size = 0;
                            if (el.TryGetProperty("size", out var sp))
                            {
                                if (sp.ValueKind == JsonValueKind.Number) size = sp.GetDecimal();
                                else if (sp.ValueKind == JsonValueKind.String) decimal.TryParse(sp.GetString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out size);
                            }
                            if (size > 0) result[asset] = size;
                        }
                    }

                    if (count < limit) break; // Reached end
                    offset += limit;
                    if (offset > 5000) break; // Safety cap
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"Failed to fetch trader positions for {walletAddress}: {ex.Message}");
            }

            return result;
        }

        public async Task<List<JsonElement>> SyncClosedPositionsAsync(string walletAddress, int limit = 100)
        {
            if (string.IsNullOrEmpty(walletAddress)) return new List<JsonElement>();
            try
            {
                TrackRequest("Positions");
                using var response = await GetWithRetryAsync($"https://data-api.polymarket.com/closed-positions?user={walletAddress}&limit={limit}&sortBy=TIMESTAMP&sortDirection=DESC");
                if (response.IsSuccessStatusCode)
                {
                    var json = await response.Content.ReadAsStringAsync();
                    using var doc = JsonDocument.Parse(json);
                    
                    var list = new List<JsonElement>();
                    if (doc.RootElement.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var el in doc.RootElement.EnumerateArray())
                            list.Add(el.Clone());
                    }
                    return list;
                }
                else
                {
                    _logger.Error($"Failed to fetch closed positions (HTTP {(int)response.StatusCode}): {response.ReasonPhrase}");
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"Failed to fetch closed positions for {walletAddress}: {ex.Message}");
            }
            return new List<JsonElement>();
        }

        /// <summary>
        /// Future placeholder for Live trading (Requires CLOB credentials context).
        /// </summary>
        public async Task<bool> PlaceOrderAsync(int accountId, string tokenId, decimal price, decimal size, string side)
        {
            TrackRequest("CLOB");
            _logger.Info($"Placing {side} order on Account {accountId} for Token {tokenId}. Size: {size} @ {price}");
            
            await Task.Delay(100);
            return true;
        }

        public async Task<List<PolyTraderSharp.Models.MarketData>> GetRecentMarketsAsync(int limit = 1000)
        {
            TrackRequest("Gamma");
            var results = new List<PolyTraderSharp.Models.MarketData>();
            try
            {
                string url = $"https://gamma-api.polymarket.com/markets?limit={limit}&order=id&ascending=false";
                using var response = await GetWithRetryAsync(url);
                if (response.IsSuccessStatusCode)
                {
                    var jsonStr = await response.Content.ReadAsStringAsync();
                    using var doc = JsonDocument.Parse(jsonStr);
                    if (doc.RootElement.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var mkt in doc.RootElement.EnumerateArray())
                        {
                            var md = new PolyTraderSharp.Models.MarketData();
                            md.Id = mkt.TryGetProperty("id", out var p1) ? p1.GetString() ?? "" : "";
                            md.ConditionId = mkt.TryGetProperty("conditionId", out var p2) ? p2.GetString() ?? "" : "";
                            md.Question = mkt.TryGetProperty("question", out var p3) ? p3.GetString() ?? "" : "";
                            
                            md.Active = mkt.TryGetProperty("active", out var p5) && p5.GetBoolean();
                            md.Closed = mkt.TryGetProperty("closed", out var p6) && p6.GetBoolean();
                            md.ClobTokenIds = mkt.TryGetProperty("clobTokenIds", out var p7) ? p7.GetString() ?? "" : "";
                            
                            md.Slug = mkt.TryGetProperty("slug", out var p4) ? p4.GetString() ?? "" : "";
                            if (mkt.TryGetProperty("events", out var evts) && evts.ValueKind == JsonValueKind.Array && evts.GetArrayLength() > 0)
                            {
                                var evSlug = evts[0].TryGetProperty("slug", out var evp) ? evp.GetString() : "";
                                if (!string.IsNullOrEmpty(evSlug)) md.Slug = evSlug;
                                
                                md.NegRisk = evts[0].TryGetProperty("enableNegRisk", out var pNeg) && pNeg.ValueKind == JsonValueKind.True;
                            }
                            
                            if (mkt.TryGetProperty("endDate", out var ep) && ep.ValueKind == JsonValueKind.String)
                            {
                                if (DateTime.TryParse(ep.GetString(), null, System.Globalization.DateTimeStyles.RoundtripKind, out var endDt))
                                {
                                    md.EndDate = endDt.ToUniversalTime();
                                }
                            }
                            
                            results.Add(md);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"Failed to fetch recent markets: {ex.Message}");
            }
            return results;
        }

        public async Task<PolyTraderSharp.Models.MarketData?> GetMarketByTokenIdAsync(string tokenId)
        {
            TrackRequest("Gamma");
            if (string.IsNullOrEmpty(tokenId)) return null;

            try
            {
                // Must use clob_token_ids! If you use clobTokenIds it ignores it and returns the oldest market (Joe Biden)
                string url = $"https://gamma-api.polymarket.com/markets?clob_token_ids={tokenId}";
                using var response = await GetWithRetryAsync(url);
                if (response.IsSuccessStatusCode)
                {
                    var jsonStr = await response.Content.ReadAsStringAsync();
                    using var doc = JsonDocument.Parse(jsonStr);
                    if (doc.RootElement.ValueKind == JsonValueKind.Array && doc.RootElement.GetArrayLength() > 0)
                    {
                        var mkt = doc.RootElement[0];
                        var md = new PolyTraderSharp.Models.MarketData();
                        md.Id = mkt.TryGetProperty("id", out var p1) ? p1.GetString() ?? "" : "";
                        md.ConditionId = mkt.TryGetProperty("conditionId", out var p2) ? p2.GetString() ?? "" : "";
                        md.Question = mkt.TryGetProperty("question", out var p3) ? p3.GetString() ?? "" : "";
                        
                        md.Active = mkt.TryGetProperty("active", out var p5) && p5.GetBoolean();
                        md.Closed = mkt.TryGetProperty("closed", out var p6) && p6.GetBoolean();
                        md.ClobTokenIds = mkt.TryGetProperty("clobTokenIds", out var p7) ? (p7.ValueKind == JsonValueKind.String ? p7.GetString() ?? "" : p7.GetRawText()) : "";
                        md.Outcomes = mkt.TryGetProperty("outcomes", out var p8) ? (p8.ValueKind == JsonValueKind.String ? p8.GetString() ?? "" : p8.GetRawText()) : "";
                        
                        md.Slug = mkt.TryGetProperty("slug", out var p4) ? p4.GetString() ?? "" : "";
                        if (mkt.TryGetProperty("events", out var evts) && evts.ValueKind == JsonValueKind.Array && evts.GetArrayLength() > 0)
                        {
                            var evt = evts[0];
                            var evSlug = evt.TryGetProperty("slug", out var evp) ? evp.GetString() : "";
                            if (!string.IsNullOrEmpty(evSlug)) md.Slug = evSlug;
                            
                            if (evt.TryGetProperty("enableNegRisk", out var pNeg) && pNeg.ValueKind == JsonValueKind.True)
                            {
                                md.NegRisk = true;
                            }
                            
                            if (evt.TryGetProperty("endDate", out var et) && DateTime.TryParse(et.GetString(), out var dt))
                            {
                                md.EndDate = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
                            }
                        }
                        else
                        {
                            if (mkt.TryGetProperty("endDate", out var et) && DateTime.TryParse(et.GetString(), out var dt))
                            {
                                md.EndDate = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
                            }
                        }
                        
                        // Security Validation: Ensure the API actually returned the market we asked for!
                        if (string.IsNullOrEmpty(md.ClobTokenIds) || !md.ClobTokenIds.Contains(tokenId))
                        {
                            _logger.Warning($"GetMarketByTokenIdAsync: API returned a mismatching market '{md.Question}' for Token {tokenId}. Skipping.");
                            return null;
                        }

                        return md;
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"Failed to fetch market by token ID ({tokenId}): {ex.Message}");
            }
            return null;
        }

        public async Task<List<PolyTraderSharp.Models.MarketData>> GetMarketsByEventSlugAsync(string slug)
        {
            TrackRequest("Gamma");
            var results = new List<PolyTraderSharp.Models.MarketData>();
            if (string.IsNullOrEmpty(slug)) return results;

            try
            {
                string url = $"https://gamma-api.polymarket.com/events?slug={slug}";
                using var response = await GetWithRetryAsync(url);
                if (response.IsSuccessStatusCode)
                {
                    var jsonStr = await response.Content.ReadAsStringAsync();
                    using var doc = JsonDocument.Parse(jsonStr);
                    if (doc.RootElement.ValueKind == JsonValueKind.Array && doc.RootElement.GetArrayLength() > 0)
                    {
                        var ev = doc.RootElement[0];
                        if (ev.TryGetProperty("markets", out var marketsArr) && marketsArr.ValueKind == JsonValueKind.Array)
                        {
                            foreach (var mkt in marketsArr.EnumerateArray())
                            {
                                var md = new PolyTraderSharp.Models.MarketData();
                                md.Id = mkt.TryGetProperty("id", out var p1) ? p1.GetString() ?? "" : "";
                                md.ConditionId = mkt.TryGetProperty("conditionId", out var p2) ? p2.GetString() ?? "" : "";
                                md.Question = mkt.TryGetProperty("question", out var p3) ? p3.GetString() ?? "" : "";
                                md.Active = mkt.TryGetProperty("active", out var p5) && p5.GetBoolean();
                                md.Closed = mkt.TryGetProperty("closed", out var p6) && p6.GetBoolean();
                                md.ClobTokenIds = mkt.TryGetProperty("clobTokenIds", out var p7) ? (p7.ValueKind == JsonValueKind.String ? p7.GetString() ?? "" : p7.GetRawText()) : "";
                                md.Outcomes = mkt.TryGetProperty("outcomes", out var p8) ? (p8.ValueKind == JsonValueKind.String ? p8.GetString() ?? "" : p8.GetRawText()) : "";
                                md.Slug = slug;
                                
                                md.NegRisk = ev.TryGetProperty("enableNegRisk", out var evNeg) && evNeg.ValueKind == JsonValueKind.True;
                                
                                if (mkt.TryGetProperty("events", out var evts) && evts.ValueKind == JsonValueKind.Array && evts.GetArrayLength() > 0)
                                {
                                    var evSlug = evts[0].TryGetProperty("slug", out var evp) ? evp.GetString() : "";
                                    if (!string.IsNullOrEmpty(evSlug)) md.Slug = evSlug;
                                    
                                    // if missing on event root but present in nested events (rare), fallback to it
                                    if (!md.NegRisk && evts[0].TryGetProperty("enableNegRisk", out var pNeg) && pNeg.ValueKind == JsonValueKind.True)
                                    {
                                        md.NegRisk = true;
                                    }
                                }

                                if (mkt.TryGetProperty("endDate", out var ep) && ep.ValueKind == JsonValueKind.String)
                                {
                                    if (DateTime.TryParse(ep.GetString(), null, System.Globalization.DateTimeStyles.RoundtripKind, out var endDt))
                                    {
                                        md.EndDate = endDt.ToUniversalTime();
                                    }
                                }
                                
                                results.Add(md);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"Failed to fetch markets by slug ({slug}): {ex.Message}");
            }
            return results;
        }

        public async Task<string> ResolveEventSlugAsync(string fallbackSlug, string tokenId)
        {
            if (string.IsNullOrEmpty(tokenId)) return fallbackSlug;

            try
            {
                // Gamma API will resolve the market object along with its parent event properties
                string url = $"https://gamma-api.polymarket.com/markets?clob_token_ids={tokenId}";
                using var response = await GetWithRetryAsync(url);
                if (response.IsSuccessStatusCode)
                {
                    var jsonStr = await response.Content.ReadAsStringAsync();
                    using var doc = JsonDocument.Parse(jsonStr);
                    if (doc.RootElement.ValueKind == JsonValueKind.Array && doc.RootElement.GetArrayLength() > 0)
                    {
                        var mkt = doc.RootElement[0];
                        if (mkt.TryGetProperty("events", out var evts) && evts.ValueKind == JsonValueKind.Array && evts.GetArrayLength() > 0)
                        {
                            var evSlug = evts[0].TryGetProperty("slug", out var evs) ? evs.GetString() : "";
                            if (!string.IsNullOrEmpty(evSlug)) return evSlug;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"Error resolving Event Slug for Token {tokenId}: {ex.Message}");
            }

            return fallbackSlug;
        }


        public async Task<List<PolyTraderSharp.Models.CopySignal>> ParseBlockchainTransactionAsync(string txHash, string rpcUrl, string masterWallet)
        {
            var results = new List<PolyTraderSharp.Models.CopySignal>();
            try
            {
                // Convert wss:// to https://
                if (rpcUrl.StartsWith("wss://")) rpcUrl = "https://" + rpcUrl.Substring(6);

                var rpcPayload = new
                {
                    jsonrpc = "2.0",
                    method = "eth_getTransactionReceipt",
                    @params = new object[] { txHash },
                    id = 1
                };

                using var request = new HttpRequestMessage(HttpMethod.Post, rpcUrl);
                request.Content = new StringContent(JsonSerializer.Serialize(rpcPayload), System.Text.Encoding.UTF8, "application/json");

                using var response = await _httpClient.SendAsync(request);
                if (!response.IsSuccessStatusCode) return results;

                var jsonStr = await response.Content.ReadAsStringAsync();
                using var doc = JsonDocument.Parse(jsonStr);

                if (!doc.RootElement.TryGetProperty("result", out var result) || result.ValueKind != JsonValueKind.Object)
                    return results;

                if (!result.TryGetProperty("logs", out var logs) || logs.ValueKind != JsonValueKind.Array)
                    return results;

                string rxFrom = "";
                if (result.TryGetProperty("from", out var fVal) && fVal.ValueKind == JsonValueKind.String)
                    rxFrom = fVal.GetString()?.ToLowerInvariant() ?? "";

                decimal usdcAmount = 0m;
                string action = "";
                
                // Track parsed CTF transfers: Dictionary<TokenId, Shares>
                var parsedTransfers = new Dictionary<string, decimal>();

                string masterWalletLower = masterWallet.ToLowerInvariant().Replace("0x", "");
                string masterWalletPadded = "0x000000000000000000000000" + masterWalletLower;
                string ctfExchangePadded = "0x0000000000000000000000004bfb41d5b3570defd03c39a9a4d8de6bd8b8982e";
                bool isMasterTxOwner = rxFrom == ("0x" + masterWalletLower);

                foreach (var log in logs.EnumerateArray())
                {
                    string address = log.GetProperty("address").GetString()?.ToLowerInvariant() ?? "";
                    
                    if (!log.TryGetProperty("topics", out var topicsArr) || topicsArr.ValueKind != JsonValueKind.Array || topicsArr.GetArrayLength() == 0) continue;
                    
                    var topics = topicsArr.EnumerateArray().Select(t => t.GetString()?.ToLowerInvariant()).ToList();
                    string data = log.GetProperty("data").GetString()?.ToLowerInvariant() ?? "0x";

                    string topic0 = topics[0] ?? "";

                    // USDC Transfer (or USDC.e)
                    if (topic0 == "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
                    {
                        if (address == "0x2791bca1f2de4661ed88a30c99a7a9449aa84174" || address == "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359")
                        {
                            if (topics.Count >= 3)
                            {
                                string fromTopic = topics[1] ?? "";
                                string toTopic = topics[2] ?? "";

                                if (isMasterTxOwner || fromTopic == masterWalletPadded || toTopic == masterWalletPadded || fromTopic == ctfExchangePadded || toTopic == ctfExchangePadded)
                                {
                                    string cleanData = data.Replace("0x", "");
                                    if (cleanData.Length >= 64)
                                    {
                                        var amountBI = System.Numerics.BigInteger.Parse("0" + cleanData.Substring(0, 64), System.Globalization.NumberStyles.HexNumber);
                                        decimal amount = (decimal)amountBI / 1_000_000m; // 6 decimals USDC
                                        usdcAmount = Math.Max(usdcAmount, amount);
                                        
                                        if (toTopic == ctfExchangePadded) action = "BUY"; 
                                        else if (fromTopic == ctfExchangePadded) action = "SELL";
                                        else if (fromTopic == masterWalletPadded) action = "BUY"; 
                                        else if (toTopic == masterWalletPadded) action = "SELL"; 
                                    }
                                }
                            }
                        }
                    }

                    // CTF TransferSingle
                    if (topic0 == "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62")
                    {
                        if (address == "0x4d97dcd97ec945f40cf65f87097ace5ea0476045")
                        {
                            if (topics.Count >= 4)
                            {
                                string fromTopic = topics[2] ?? "";
                                string toTopic = topics[3] ?? "";

                                if (isMasterTxOwner || fromTopic == masterWalletPadded || toTopic == masterWalletPadded)
                                {
                                    string cleanData = data.Replace("0x", "");
                                    if (cleanData.Length >= 128)
                                    {
                                        string idHex = cleanData.Substring(0, 64);
                                        string valueHex = cleanData.Substring(64, 64);

                                        var idBI = System.Numerics.BigInteger.Parse("0" + idHex, System.Globalization.NumberStyles.HexNumber);
                                        var valueBI = System.Numerics.BigInteger.Parse("0" + valueHex, System.Globalization.NumberStyles.HexNumber);

                                        string tid = idBI.ToString();
                                        decimal sh = (decimal)valueBI / 1_000_000m; // 6 decimals CTF
                                        
                                        if (parsedTransfers.ContainsKey(tid)) parsedTransfers[tid] += sh;
                                        else parsedTransfers[tid] = sh;
                                    }
                                }
                            }
                        }
                    }
                    
                    // CTF TransferBatch
                    if (topic0 == "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7ce")
                    {
                        if (address == "0x4d97dcd97ec945f40cf65f87097ace5ea0476045")
                        {
                            if (topics.Count >= 4)
                            {
                                string fromTopic = topics[2] ?? "";
                                string toTopic = topics[3] ?? "";

                                if (isMasterTxOwner || fromTopic == masterWalletPadded || toTopic == masterWalletPadded)
                                {
                                    string cleanData = data.Replace("0x", "");
                                    if (cleanData.Length >= 256)
                                    {
                                        try
                                        {
                                            var chunks = Enumerable.Range(0, cleanData.Length / 64).Select(i => cleanData.Substring(i * 64, 64)).ToList();
                                            if (chunks.Count >= 4)
                                            {
                                                int idsOffsetWord = int.Parse(chunks[0], System.Globalization.NumberStyles.HexNumber) / 32;
                                                int valsOffsetWord = int.Parse(chunks[1], System.Globalization.NumberStyles.HexNumber) / 32;
                                                
                                                if (idsOffsetWord < chunks.Count && valsOffsetWord < chunks.Count)
                                                {
                                                    int idsLen = int.Parse(chunks[idsOffsetWord], System.Globalization.NumberStyles.HexNumber);
                                                    int valsLen = int.Parse(chunks[valsOffsetWord], System.Globalization.NumberStyles.HexNumber);
                                                    
                                                    int maxLen = Math.Min(idsLen, valsLen);
                                                    for (int i = 0; i < maxLen; i++)
                                                    {
                                                        if (idsOffsetWord + 1 + i < chunks.Count && valsOffsetWord + 1 + i < chunks.Count)
                                                        {
                                                            string idHex = chunks[idsOffsetWord + 1 + i];
                                                            string valHex = chunks[valsOffsetWord + 1 + i];
                                                            
                                                            var idBI = System.Numerics.BigInteger.Parse("0" + idHex, System.Globalization.NumberStyles.HexNumber);
                                                            var valueBI = System.Numerics.BigInteger.Parse("0" + valHex, System.Globalization.NumberStyles.HexNumber);
                                                            
                                                            string tid = idBI.ToString();
                                                            decimal sh = (decimal)valueBI / 1_000_000m;
                                                            
                                                            if (parsedTransfers.ContainsKey(tid)) parsedTransfers[tid] += sh;
                                                            else parsedTransfers[tid] = sh;
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                        catch (Exception ex)
                                        {
                                            _logger.Error($"Error parsing TransferBatch for TX {txHash}: {ex.Message}");
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                if (parsedTransfers.Count > 0 && !string.IsNullOrEmpty(action) && usdcAmount > 0)
                {
                    decimal totalSharesForAllTokens = parsedTransfers.Values.Sum();
                    decimal globalAvgPrice = totalSharesForAllTokens > 0 ? usdcAmount / totalSharesForAllTokens : 0;
                    
                    // Filter: Redeems yield exactly $1.00 USD per share. Merges also yield $1.00 USD for a full set.
                    // If the master trader "sells" at >= 0.99 on-chain, it is guaranteed to be a Redeem/Winnings Claim, NOT an orderbook trade.
                    // We must filter this out so the copy trading engine doesn't dump our tickets at market price!
                    if (action == "SELL" && globalAvgPrice >= 0.99m)
                    {
                        _logger.Debug($"FastTrack Parser: Ignored Fake SELL (Redeem/Merge) with Return Price ${globalAvgPrice:F3} for TX {txHash}");
                        return results;
                    }

                    if (globalAvgPrice > 0.999m) globalAvgPrice = 0.99m;

                    foreach (var pt in parsedTransfers)
                    {
                        var signal = new PolyTraderSharp.Models.CopySignal
                        {
                            TokenId = pt.Key,
                            Side = action,
                            Size = pt.Value,
                            Price = globalAvgPrice, 
                            Timestamp = DateTime.UtcNow
                        };
                        results.Add(signal);
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"Blockchain Parser Error: {ex.Message}");
            }
            return results;
        }
    }
}
