using System.Net.Http.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Caching.Memory;
using Predictalytics.Application.Interfaces;
using Predictalytics.Domain.Enums;
using Predictalytics.Infrastructure.Services;

namespace Predictalytics.Infrastructure.Providers.Polymarket;

/// <summary>
/// HTTP client for Polymarket Data API.
/// All endpoints use https://data-api.polymarket.com
/// </summary>
public class PolymarketApiClient
{
    private readonly HttpClient _client;
    private readonly HttpClient _gammaClient;
    private readonly HttpClient _clobClient;
    private readonly IRateLimiter _rateLimiter;
    private readonly IEgressPoolService _egressPool;
    private readonly IMemoryCache _cache;
    private readonly ILogger<PolymarketApiClient> _logger;

    private const string DataApiBase = "https://data-api.polymarket.com";
    private const string GammaApiBase = "https://gamma-api.polymarket.com";
    private const string ClobApiBase = "https://clob.polymarket.com";

    public PolymarketApiClient(IHttpClientFactory httpFactory, IRateLimiter rateLimiter, IEgressPoolService egressPool, IMemoryCache cache, ILogger<PolymarketApiClient> logger)
    {
        _client = httpFactory.CreateClient("PolymarketData");
        _client.BaseAddress = new Uri(DataApiBase);
        _client.DefaultRequestHeaders.Add("Accept", "application/json");

        _gammaClient = httpFactory.CreateClient("PolymarketGamma");
        _gammaClient.BaseAddress = new Uri(GammaApiBase);
        _gammaClient.DefaultRequestHeaders.Add("Accept", "application/json");

        _clobClient = httpFactory.CreateClient("PolymarketClob");
        _clobClient.BaseAddress = new Uri(ClobApiBase);
        _clobClient.DefaultRequestHeaders.Add("Accept", "application/json");

        _rateLimiter = rateLimiter;
        _egressPool = egressPool;
        _cache = cache;
        _logger = logger;
    }

    public async Task<List<PolymarketTradeResponse>> GetTradesAsync(string walletAddress, int limit = 1000, CancellationToken ct = default)
    {
        var url = $"/activity?user={walletAddress}&limit={limit}";
        return await ExecuteWithRetryAsync<List<PolymarketTradeResponse>>(_client, url, "Data", ct) ?? [];
    }

    public async Task<List<PolymarketTradeResponse>> GetTradesPagedAsync(string walletAddress, int limit = 500, CancellationToken ct = default)
    {
        var allTrades = new List<PolymarketTradeResponse>();
        long? endTimestamp = null;
        
        while (true)
        {
            var url = $"/activity?user={walletAddress}&limit={limit}";
            if (endTimestamp.HasValue)
            {
                url += $"&end={endTimestamp.Value}";
            }

            var batch = await ExecuteWithRetryAsync<List<PolymarketTradeResponse>>(_client, url, "Data", ct);
            if (batch == null || batch.Count == 0)
            {
                break;
            }

            allTrades.AddRange(batch);

            if (batch.Count < limit)
            {
                break;
            }

            var oldestTimestamp = batch.Min(t => t.Timestamp);
            endTimestamp = oldestTimestamp - 1;
        }

        return allTrades;
    }

    public async Task<List<PolymarketTradeResponse>> GetMarketTradesAsync(string conditionId, int limit = 1000, int offset = 0, CancellationToken ct = default)
    {
        var url = $"/trades?condition_id={conditionId}&limit={limit}&offset={offset}";
        return await ExecuteWithRetryAsync<List<PolymarketTradeResponse>>(_client, url, "Data", ct) ?? [];
    }

    public async Task<List<PolymarketPositionResponse>> GetPositionsAsync(string walletAddress, CancellationToken ct = default)
    {
        var url = $"/positions?user={walletAddress}&sizeThreshold=0.1&sortBy=CURRENT&sortOrder=DESC";
        return await ExecuteWithRetryAsync<List<PolymarketPositionResponse>>(_client, url, "Data", ct) ?? [];
    }

    public async Task<GammaMarketResponse?> GetMarketAsync(string conditionId, CancellationToken ct = default)
    {
        var cacheKey = $"market-{conditionId}";
        if (_cache.TryGetValue<GammaMarketResponse>(cacheKey, out var cached))
        {
            return cached;
        }

        var url = $"/markets?condition_id={conditionId}";
        var results = await ExecuteWithRetryAsync<List<GammaMarketResponse>>(_gammaClient, url, "Gamma", ct);
        var market = results?.FirstOrDefault();

        if (market != null)
        {
            _cache.Set(cacheKey, market, TimeSpan.FromMinutes(30));
        }

        return market;
    }

    public async Task<GammaEventResponse?> GetEventAsync(long eventId, CancellationToken ct = default)
    {
        var cacheKey = $"event-{eventId}";
        if (_cache.TryGetValue<GammaEventResponse>(cacheKey, out var cached))
        {
            return cached;
        }

        var url = $"/events?id={eventId}";
        var results = await ExecuteWithRetryAsync<List<GammaEventResponse>>(_gammaClient, url, "Gamma", ct);
        var ev = results?.FirstOrDefault();

        if (ev != null)
        {
            _cache.Set(cacheKey, ev, TimeSpan.FromMinutes(60));
        }

        return ev;
    }

    /// <summary>
    /// Fetch a batch of events (and their nested markets) from the Gamma API with pagination.
    /// Supports offset-based pagination via the offset parameter.
    /// </summary>
    public async Task<List<GammaEventResponse>> GetEventsAsync(int limit = 100, int offset = 0, bool includeClosed = false, CancellationToken ct = default)
    {
        var activeOnly = !includeClosed;
        var url = $"/events?limit={limit}&offset={offset}&active={activeOnly.ToString().ToLower()}&closed={includeClosed.ToString().ToLower()}";
        _logger.LogDebug("Fetching events: {Url}", url);
        var result = await ExecuteWithRetryAsync<List<GammaEventResponse>>(_gammaClient, url, "Gamma", ct);
        _logger.LogInformation("Fetched {Count} events (offset={Offset}, closed={Closed})", result?.Count ?? 0, offset, includeClosed);
        return result ?? [];
    }

    /// <summary>
    /// Fetch top holders for a specific market (conditionId) from the Data API.
    /// Returns holders grouped by token (outcome).
    /// </summary>
    public async Task<List<HoldersResponse>> GetHoldersAsync(string conditionId, int limit = 20, CancellationToken ct = default)
    {
        var url = $"/holders?market={conditionId}&limit={limit}";
        _logger.LogDebug("Fetching holders: {Url}", url);
        var result = await ExecuteWithRetryAsync<List<HoldersResponse>>(_client, url, "Data", ct);
        _logger.LogInformation("Fetched holders for {Market}: {Count} token groups",
            conditionId.Length > 12 ? conditionId[..12] + "..." : conditionId, result?.Count ?? 0);
        return result ?? [];
    }

    /// <summary>
    /// Get leaderboard from the official Polymarket Data API v1.
    /// Endpoint: GET https://data-api.polymarket.com/v1/leaderboard
    /// </summary>
    public async Task<List<LeaderboardEntry>> GetLeaderboardAsync(
        int limit = 50,
        string timePeriod = "ALL",
        string orderBy = "PNL",
        string category = "OVERALL",
        CancellationToken ct = default)
    {
        var url = $"/v1/leaderboard?limit={Math.Min(limit, 50)}&time_period={timePeriod}&order_by={orderBy}&category={category}";
        _logger.LogDebug("Fetching leaderboard: {Url}", url);
        var result = await ExecuteWithRetryAsync<List<LeaderboardEntry>>(_client, url, "Data", ct);
        _logger.LogInformation("Leaderboard returned {Count} entries", result?.Count ?? 0);
        return result ?? [];
    }

    /// <summary>
    /// Fetch CLOB orderbook for a given token ID.
    /// </summary>
    public async Task<OrderBookResponse?> GetOrderBookAsync(string tokenId, CancellationToken ct = default)
    {
        var url = $"/book?token_id={tokenId}";
        _logger.LogDebug("Fetching orderbook for token: {TokenId}", tokenId);
        return await ExecuteWithRetryAsync<OrderBookResponse>(_clobClient, url, "CLOB", ct);
    }

    private async Task<T?> ExecuteWithRetryAsync<T>(HttpClient client, string url, string endpointGroup, CancellationToken ct, int attempt = 1)
    {
        var channelId = _egressPool.GetNextChannelId();
        await _rateLimiter.WaitAsync(PlatformType.Polymarket, ct, endpointGroup, channelId);

        try
        {
            using var request = new HttpRequestMessage(HttpMethod.Get, url);
            if (!string.IsNullOrEmpty(channelId))
            {
                request.Options.Set(EgressRequestOptions.ChannelIdKey, channelId);
            }

            var response = await client.SendAsync(request, ct);

            if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
            {
                TimeSpan? retryAfter = null;
                if (response.Headers.RetryAfter != null)
                {
                    retryAfter = response.Headers.RetryAfter.Delta ?? 
                                (response.Headers.RetryAfter.Date.HasValue 
                                    ? response.Headers.RetryAfter.Date.Value - DateTimeOffset.UtcNow 
                                    : null);
                }

                var waitTime = retryAfter ?? TimeSpan.FromSeconds(30);
                if (waitTime.TotalSeconds < 5)
                {
                    _logger.LogWarning("Got 429 but Retry-After was {RawWait}s. Enforcing 30s minimum.", waitTime.TotalSeconds);
                    waitTime = TimeSpan.FromSeconds(30);
                }

                _logger.LogWarning("⚠️ Rate limit exceeded (429) for Polymarket {Group} (Channel: {Channel}). Pausing for {WaitTime}s...", endpointGroup, channelId ?? "Default", (int)waitTime.TotalSeconds);
                
                _rateLimiter.ReportRateLimitExceeded(PlatformType.Polymarket, waitTime, endpointGroup, channelId);
                
                if (attempt < 3)
                {
                    _logger.LogWarning("Retrying {Url} (attempt {NextAttempt})...", url, attempt + 1);
                    return await ExecuteWithRetryAsync<T>(client, url, endpointGroup, ct, attempt + 1);
                }
                
                return default;
            }

            if ((int)response.StatusCode == 422)
            {
                _logger.LogInformation("End of data reached (422) for {Url}. Stopping pagination.", url);
                return default;
            }

            response.EnsureSuccessStatusCode();
            return await response.Content.ReadFromJsonAsync<T>(cancellationToken: ct);
        }
        catch (Exception ex)
        {
            if (ex is HttpRequestException hex && hex.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
            {
                _logger.LogCritical("Unhandled 429 in PolymarketApiClient for {Url}. This should have been caught by the status code check.", url);
            }
            _logger.LogError(ex, "Failed to fetch from {Url} (attempt {Attempt}, Channel: {Channel})", url, attempt, channelId ?? "Default");
            return default;
        }
    }

    public async Task<List<PriceHistoryEntry>> GetPricesHistoryAsync(string clobTokenId, string interval = "6h", CancellationToken ct = default)
    {
        var url = $"/prices-history?market={clobTokenId}&interval={interval}";
        var result = await ExecuteWithRetryAsync<PolymarketPriceHistoryResponse>(_clobClient, url, "Clob", ct);
        return result?.History ?? [];
    }
}

public class PolymarketPriceHistoryResponse
{
    [JsonPropertyName("history")] public List<PriceHistoryEntry> History { get; set; } = [];
}

public class PriceHistoryEntry
{
    [JsonPropertyName("t")] public long Timestamp { get; set; }
    [JsonPropertyName("p")] public double Price { get; set; }
}
