using System.Collections;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Web;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Accounts;
using Managing.Infrastructure.Evm.Models.Proxy;
using Microsoft.Extensions.Options;
using static Managing.Common.Enums;

namespace Managing.Infrastructure.Evm.Services
{
    public class Web3ProxySettings
    {
        public string BaseUrl { get; set; } = "http://localhost:3000";
    }

    public class Web3ProxyService : IWeb3ProxyService
    {
        private readonly HttpClient _httpClient;
        private readonly Web3ProxySettings _settings;
        private readonly JsonSerializerOptions _jsonOptions;

        public Web3ProxyService(IOptions<Web3ProxySettings> options)
        {
            _httpClient = new HttpClient();
            _settings = options.Value;
            _jsonOptions = new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            };
        }

        public async Task<T> CallPrivyServiceAsync<T>(string endpoint, object payload)
        {
            if (!endpoint.StartsWith("/"))
            {
                endpoint = $"/{endpoint}";
            }

            var url = $"{_settings.BaseUrl}/api/privy{endpoint}";

            try
            {
                var response = await _httpClient.PostAsJsonAsync(url, payload, _jsonOptions);

                if (!response.IsSuccessStatusCode)
                {
                    await HandleErrorResponse(response);
                }

                return await response.Content.ReadFromJsonAsync<T>(_jsonOptions);
            }
            catch (Exception ex) when (!(ex is Web3ProxyException))
            {
                SentrySdk.CaptureException(ex);
                throw new Web3ProxyException($"Failed to call Privy service at {endpoint}: {ex.Message}");
            }
        }

        public async Task<T> GetPrivyServiceAsync<T>(string endpoint, object payload = null)
        {
            if (!endpoint.StartsWith("/"))
            {
                endpoint = $"/{endpoint}";
            }

            var url = $"{_settings.BaseUrl}/api/privy{endpoint}";

            if (payload != null)
            {
                url += BuildQueryString(payload);
            }

            try
            {
                var response = await _httpClient.GetAsync(url);

                if (!response.IsSuccessStatusCode)
                {
                    await HandleErrorResponse(response);
                }

                return await response.Content.ReadFromJsonAsync<T>(_jsonOptions);
            }
            catch (Exception ex) when (!(ex is Web3ProxyException))
            {
                SentrySdk.CaptureException(ex);
                throw new Web3ProxyException($"Failed to get Privy service at {endpoint}: {ex.Message}");
            }
        }

        public async Task<T> CallGmxServiceAsync<T>(string endpoint, object payload)
        {
            if (!endpoint.StartsWith("/"))
            {
                endpoint = $"/{endpoint}";
            }

            var url = $"{_settings.BaseUrl}/api/gmx{endpoint}";

            try
            {
                var response = await _httpClient.PostAsJsonAsync(url, payload, _jsonOptions);

                if (!response.IsSuccessStatusCode)
                {
                    await HandleErrorResponse(response);
                }

                return await response.Content.ReadFromJsonAsync<T>(_jsonOptions);
            }
            catch (Exception ex) when (!(ex is Web3ProxyException))
            {
                SentrySdk.CaptureException(ex);
                throw new Web3ProxyException($"Failed to call GMX service at {endpoint}: {ex.Message}");
            }
        }

        public async Task<T> GetGmxServiceAsync<T>(string endpoint, object payload = null)
        {
            if (!endpoint.StartsWith("/"))
            {
                endpoint = $"/{endpoint}";
            }

            var url = $"{_settings.BaseUrl}/api/gmx{endpoint}";

            if (payload != null)
            {
                url += BuildQueryString(payload);
            }

            try
            {
                var response = await _httpClient.GetAsync(url);

                if (!response.IsSuccessStatusCode)
                {
                    await HandleErrorResponse(response);
                }

                return await response.Content.ReadFromJsonAsync<T>(_jsonOptions);
            }
            catch (Exception ex) when (!(ex is Web3ProxyException))
            {
                SentrySdk.CaptureException(ex);
                throw new Web3ProxyException($"Failed to get GMX service at {endpoint}: {ex.Message}");
            }
        }

        public async Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(string account)
        {
            var payload = new { account };
            var response = await GetGmxServiceAsync<ClaimingFeesResponse>("/claimable-summary", payload);


            if (response.Data == null)
            {
                throw new Web3ProxyException("GMX claimable summary data is null");
            }

            // Map from Web3Proxy response model to domain model
            return new GmxClaimableSummary
            {
                ClaimableFundingFees = new FundingFeesData
                {
                    TotalUsdc = response.Data.ClaimableFundingFees.TotalUsdc
                },
                ClaimableUiFees = new UiFeesData
                {
                    TotalUsdc = response.Data.ClaimableUiFees.TotalUsdc
                },
                RebateStats = new RebateStatsData
                {
                    TotalRebateUsdc = response.Data.RebateStats.TotalRebateUsdc,
                    DiscountUsdc = response.Data.RebateStats.DiscountUsdc,
                    RebateFactor = response.Data.RebateStats.RebateFactor,
                    DiscountFactor = response.Data.RebateStats.DiscountFactor
                }
            };
        }

        public async Task<SwapInfos> SwapGmxTokensAsync(string account, Ticker fromTicker, Ticker toTicker,
            double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5)
        {
            var payload = new
            {
                account,
                fromTicker = fromTicker.ToString(),
                toTicker = toTicker.ToString(),
                amount,
                orderType,
                triggerRatio,
                allowedSlippage
            };

            var response = await CallGmxServiceAsync<GmxSwapResponse>("/swap-tokens", payload);

            if (response == null)
            {
                throw new Web3ProxyException("GMX swap response is null");
            }

            // Map from infrastructure model to domain model
            return new SwapInfos
            {
                Success = response.Success,
                Hash = response.Hash,
                Message = response.Message,
                Error = null, // GmxSwapResponse doesn't have Error property
                ErrorType = response.ErrorType,
                Suggestion = response.Suggestion
            };
        }

        public async Task<SwapInfos> SendTokenAsync(string senderAddress, string recipientAddress, Ticker ticker,
            decimal amount, int? chainId = null)
        {
            var payload = new
            {
                senderAddress,
                recipientAddress,
                ticker = ticker.ToString(),
                amount = amount.ToString(), // Convert decimal to string for bigint compatibility
                chainId
            };

            var response = await CallPrivyServiceAsync<Web3ProxyTokenSendResponse>("/send-token", payload);

            if (response == null)
            {
                throw new Web3ProxyException("Token send response is null");
            }

            // Map from infrastructure model to domain model
            return new SwapInfos
            {
                Success = response.Success,
                Hash = response.Hash,
                Message = null, // Web3ProxyTokenSendResponse doesn't have Message property
                Error = response.Error,
                ErrorType = null, // Web3ProxyTokenSendResponse doesn't have ErrorType property
                Suggestion = null // Web3ProxyTokenSendResponse doesn't have Suggestion property
            };
        }

        public async Task<List<Balance>> GetWalletBalanceAsync(string address, Ticker[] assets, string[] chains)
        {
            var payload = new
            {
                address,
                asset = assets,
                chain = chains
            };

            var response = await GetPrivyServiceAsync<Web3ProxyBalanceResponse>("/wallet-balance", payload);

            if (response == null)
            {
                throw new Web3ProxyException("Wallet balance response is null");
            }

            if (!response.Success)
            {
                throw new Web3ProxyException($"Wallet balance request failed: {response.Error}");
            }

            return response.Balances ?? new List<Balance>();
        }

        private async Task HandleErrorResponse(HttpResponseMessage response)
        {
            var statusCode = (int)response.StatusCode;

            try
            {
                // Try to parse as the Web3Proxy error format (success: false, error: string)
                var content = await response.Content.ReadAsStringAsync();
                var errorResponse = await response.Content.ReadFromJsonAsync<Web3ProxyResponse>(_jsonOptions);

                if (errorResponse != null && !errorResponse.Success && !string.IsNullOrEmpty(errorResponse.Error))
                {
                    // Handle the standard Web3Proxy error format
                    throw new Web3ProxyException(errorResponse.Error);
                }

                // Fallback for other error formats
                try
                {
                    // Try to parse as structured error if it doesn't match the simple format
                    var structuredErrorResponse =
                        await response.Content.ReadFromJsonAsync<Web3ProxyErrorResponse>(_jsonOptions);

                    if (structuredErrorResponse?.ErrorDetails != null)
                    {
                        structuredErrorResponse.ErrorDetails.StatusCode = statusCode;
                        throw new Web3ProxyException(structuredErrorResponse.ErrorDetails);
                    }
                }
                catch
                {
                    // If we couldn't parse as structured error, use the simple error or fallback
                    throw new Web3ProxyException($"HTTP error {statusCode}: {content}");
                }
            }
            catch (Exception ex) when (!(ex is Web3ProxyException))
            {
                SentrySdk.CaptureException(ex);
                // If we couldn't parse the error as JSON or another issue occurred
                var content = await response.Content.ReadAsStringAsync();
                throw new Web3ProxyException($"HTTP error {statusCode}: {content}");
            }
        }

        private string BuildQueryString(object payload)
        {
            var properties = payload.GetType().GetProperties();
            if (properties.Length == 0)
            {
                return string.Empty;
            }

            var queryString = new StringBuilder("?");
            bool isFirst = true;

            foreach (var prop in properties)
            {
                var value = prop.GetValue(payload);
                if (value != null)
                {
                    var paramName = prop.Name;
                    // Apply camelCase to match JSON property naming
                    paramName = char.ToLowerInvariant(paramName[0]) + paramName.Substring(1);

                    // Check if the value is an array or enumerable (but not string)
                    if (value is IEnumerable enumerable && !(value is string))
                    {
                        // Handle arrays by creating multiple query parameters with the same name
                        foreach (var item in enumerable)
                        {
                            if (!isFirst)
                            {
                                queryString.Append("&");
                            }

                            queryString.Append(HttpUtility.UrlEncode(paramName));
                            queryString.Append("=");
                            queryString.Append(HttpUtility.UrlEncode(item?.ToString() ?? ""));

                            isFirst = false;
                        }
                    }
                    else
                    {
                        // Handle single values
                        if (!isFirst)
                        {
                            queryString.Append("&");
                        }

                        queryString.Append(HttpUtility.UrlEncode(paramName));
                        queryString.Append("=");
                        queryString.Append(HttpUtility.UrlEncode(value.ToString()));

                        isFirst = false;
                    }
                }
            }

            return queryString.ToString();
        }
    }
}