using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Web;

namespace Synor.Dex
{
    /// <summary>
    /// Synor DEX SDK Client
    ///
    /// Complete decentralized exchange client with support for:
    /// - AMM swaps (constant product, stable, concentrated)
    /// - Liquidity provision
    /// - Perpetual futures (up to 100x leverage)
    /// - Order books (limit orders)
    /// - Farming & staking
    /// </summary>
    public class SynorDex : IDisposable
    {
        private readonly DexConfig _config;
        private readonly HttpClient _client;
        private readonly JsonSerializerOptions _jsonOptions;
        private bool _closed;

        public PerpsClient Perps { get; }
        public OrderBookClient OrderBook { get; }
        public FarmsClient Farms { get; }

        public SynorDex(DexConfig config)
        {
            _config = config;
            _client = new HttpClient
            {
                BaseAddress = new Uri(config.Endpoint),
                Timeout = TimeSpan.FromMilliseconds(config.Timeout)
            };
            _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", config.ApiKey);
            _client.DefaultRequestHeaders.Add("X-SDK-Version", "csharp/0.1.0");

            _jsonOptions = new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
                PropertyNameCaseInsensitive = true
            };

            Perps = new PerpsClient(this);
            OrderBook = new OrderBookClient(this);
            Farms = new FarmsClient(this);
        }

        // Token Operations

        public Task<Token> GetTokenAsync(string address)
            => GetAsync<Token>($"/tokens/{address}");

        public Task<List<Token>> ListTokensAsync()
            => GetAsync<List<Token>>("/tokens");

        public Task<List<Token>> SearchTokensAsync(string query)
            => GetAsync<List<Token>>($"/tokens/search?q={HttpUtility.UrlEncode(query)}");

        // Pool Operations

        public Task<Pool> GetPoolAsync(string tokenA, string tokenB)
            => GetAsync<Pool>($"/pools/{tokenA}/{tokenB}");

        public Task<Pool> GetPoolByIdAsync(string poolId)
            => GetAsync<Pool>($"/pools/{poolId}");

        public Task<List<Pool>> ListPoolsAsync(PoolFilter filter = null)
        {
            var parameters = new List<string>();
            if (filter != null)
            {
                if (filter.Tokens != null) parameters.Add($"tokens={string.Join(",", filter.Tokens)}");
                if (filter.MinTvl.HasValue) parameters.Add($"min_tvl={filter.MinTvl}");
                if (filter.MinVolume24h.HasValue) parameters.Add($"min_volume={filter.MinVolume24h}");
                if (filter.Verified.HasValue) parameters.Add($"verified={filter.Verified.Value.ToString().ToLower()}");
                if (filter.Limit.HasValue) parameters.Add($"limit={filter.Limit}");
                if (filter.Offset.HasValue) parameters.Add($"offset={filter.Offset}");
            }
            var path = parameters.Count > 0 ? $"/pools?{string.Join("&", parameters)}" : "/pools";
            return GetAsync<List<Pool>>(path);
        }

        // Swap Operations

        public Task<Quote> GetQuoteAsync(QuoteParams prms)
            => PostAsync<Quote>("/swap/quote", new
            {
                token_in = prms.TokenIn,
                token_out = prms.TokenOut,
                amount_in = prms.AmountIn.ToString(),
                slippage = prms.Slippage ?? 0.005
            });

        public Task<SwapResult> SwapAsync(SwapParams prms)
        {
            var deadline = prms.Deadline ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 1200;
            return PostAsync<SwapResult>("/swap", new
            {
                token_in = prms.TokenIn,
                token_out = prms.TokenOut,
                amount_in = prms.AmountIn.ToString(),
                min_amount_out = prms.MinAmountOut.ToString(),
                deadline,
                recipient = prms.Recipient
            });
        }

        // Liquidity Operations

        public Task<LiquidityResult> AddLiquidityAsync(AddLiquidityParams prms)
        {
            var deadline = prms.Deadline ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 1200;
            return PostAsync<LiquidityResult>("/liquidity/add", new
            {
                token_a = prms.TokenA,
                token_b = prms.TokenB,
                amount_a = prms.AmountA.ToString(),
                amount_b = prms.AmountB.ToString(),
                deadline,
                min_amount_a = prms.MinAmountA?.ToString(),
                min_amount_b = prms.MinAmountB?.ToString()
            });
        }

        public Task<LiquidityResult> RemoveLiquidityAsync(RemoveLiquidityParams prms)
        {
            var deadline = prms.Deadline ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 1200;
            return PostAsync<LiquidityResult>("/liquidity/remove", new
            {
                pool = prms.Pool,
                lp_amount = prms.LpAmount.ToString(),
                deadline,
                min_amount_a = prms.MinAmountA?.ToString(),
                min_amount_b = prms.MinAmountB?.ToString()
            });
        }

        public Task<List<LPPosition>> GetMyPositionsAsync()
            => GetAsync<List<LPPosition>>("/liquidity/positions");

        // Analytics

        public Task<List<OHLCV>> GetPriceHistoryAsync(string pair, string interval, int limit = 100)
            => GetAsync<List<OHLCV>>($"/analytics/candles/{pair}?interval={interval}&limit={limit}");

        public Task<List<TradeHistory>> GetTradeHistoryAsync(string pair, int limit = 50)
            => GetAsync<List<TradeHistory>>($"/analytics/trades/{pair}?limit={limit}");

        public Task<VolumeStats> GetVolumeStatsAsync()
            => GetAsync<VolumeStats>("/analytics/volume");

        public Task<TVLStats> GetTVLAsync()
            => GetAsync<TVLStats>("/analytics/tvl");

        // Lifecycle

        public async Task<bool> HealthCheckAsync()
        {
            try
            {
                var response = await GetAsync<HealthResponse>("/health");
                return response.Status == "healthy";
            }
            catch
            {
                return false;
            }
        }

        public void Close()
        {
            _closed = true;
            _client.Dispose();
        }

        public bool IsClosed => _closed;

        public void Dispose()
        {
            Close();
        }

        // Internal methods

        internal async Task<T> GetAsync<T>(string path)
        {
            CheckClosed();
            var response = await _client.GetAsync(path);
            return await HandleResponseAsync<T>(response);
        }

        internal async Task<T> PostAsync<T>(string path, object body)
        {
            CheckClosed();
            var json = JsonSerializer.Serialize(body, _jsonOptions);
            var content = new StringContent(json, Encoding.UTF8, "application/json");
            var response = await _client.PostAsync(path, content);
            return await HandleResponseAsync<T>(response);
        }

        internal async Task<T> DeleteAsync<T>(string path)
        {
            CheckClosed();
            var response = await _client.DeleteAsync(path);
            return await HandleResponseAsync<T>(response);
        }

        private async Task<T> HandleResponseAsync<T>(HttpResponseMessage response)
        {
            var body = await response.Content.ReadAsStringAsync();

            if (!response.IsSuccessStatusCode)
            {
                ErrorResponse error = null;
                try { error = JsonSerializer.Deserialize<ErrorResponse>(body, _jsonOptions); } catch { }
                throw new DexException(
                    error?.Message ?? $"HTTP {(int)response.StatusCode}",
                    error?.Code,
                    (int)response.StatusCode);
            }

            return JsonSerializer.Deserialize<T>(body, _jsonOptions);
        }

        private void CheckClosed()
        {
            if (_closed)
                throw new DexException("Client has been closed", "CLIENT_CLOSED", 0);
        }

        private class HealthResponse { public string Status { get; set; } }
        private class ErrorResponse { public string Message { get; set; } public string Code { get; set; } }
    }

    /// <summary>
    /// DEX Configuration
    /// </summary>
    public class DexConfig
    {
        public string ApiKey { get; set; }
        public string Endpoint { get; set; } = "https://dex.synor.io/v1";
        public string WsEndpoint { get; set; } = "wss://dex.synor.io/v1/ws";
        public int Timeout { get; set; } = 30000;
        public int Retries { get; set; } = 3;
        public bool Debug { get; set; } = false;
    }

    /// <summary>
    /// DEX Exception
    /// </summary>
    public class DexException : Exception
    {
        public string Code { get; }
        public int Status { get; }

        public DexException(string message, string code = null, int status = 0) : base(message)
        {
            Code = code;
            Status = status;
        }
    }

    /// <summary>
    /// Perpetual futures sub-client
    /// </summary>
    public class PerpsClient
    {
        private readonly SynorDex _dex;
        internal PerpsClient(SynorDex dex) => _dex = dex;

        public Task<List<PerpMarket>> ListMarketsAsync()
            => _dex.GetAsync<List<PerpMarket>>("/perps/markets");

        public Task<PerpMarket> GetMarketAsync(string symbol)
            => _dex.GetAsync<PerpMarket>($"/perps/markets/{symbol}");

        public Task<PerpPosition> OpenPositionAsync(OpenPositionParams prms)
            => _dex.PostAsync<PerpPosition>("/perps/positions", new
            {
                market = prms.Market,
                side = prms.Side.ToString().ToLower(),
                size = prms.Size.ToString(),
                leverage = prms.Leverage,
                order_type = prms.OrderType.ToString().ToLower(),
                margin_type = prms.MarginType.ToString().ToLower(),
                reduce_only = prms.ReduceOnly,
                limit_price = prms.LimitPrice,
                stop_loss = prms.StopLoss,
                take_profit = prms.TakeProfit
            });

        public Task<PerpPosition> ClosePositionAsync(ClosePositionParams prms)
            => _dex.PostAsync<PerpPosition>("/perps/positions/close", new
            {
                market = prms.Market,
                size = prms.Size?.ToString(),
                order_type = prms.OrderType.ToString().ToLower(),
                limit_price = prms.LimitPrice
            });

        public Task<List<PerpPosition>> GetPositionsAsync()
            => _dex.GetAsync<List<PerpPosition>>("/perps/positions");

        public Task<List<PerpOrder>> GetOrdersAsync()
            => _dex.GetAsync<List<PerpOrder>>("/perps/orders");

        public Task<List<FundingPayment>> GetFundingHistoryAsync(string market, int limit = 100)
            => _dex.GetAsync<List<FundingPayment>>($"/perps/funding/{market}?limit={limit}");

        public Task<FundingRateInfo> GetFundingRateAsync(string market)
            => _dex.GetAsync<FundingRateInfo>($"/perps/funding/{market}/current");
    }

    /// <summary>
    /// Order book sub-client
    /// </summary>
    public class OrderBookClient
    {
        private readonly SynorDex _dex;
        internal OrderBookClient(SynorDex dex) => _dex = dex;

        public Task<OrderBook> GetOrderBookAsync(string market, int depth = 20)
            => _dex.GetAsync<OrderBook>($"/orderbook/{market}?depth={depth}");

        public Task<Order> PlaceLimitOrderAsync(LimitOrderParams prms)
            => _dex.PostAsync<Order>("/orderbook/orders", new
            {
                market = prms.Market,
                side = prms.Side,
                price = prms.Price,
                size = prms.Size.ToString(),
                time_in_force = prms.TimeInForce.ToString(),
                post_only = prms.PostOnly
            });

        public Task<List<Order>> GetOpenOrdersAsync(string market = null)
        {
            var path = market != null ? $"/orderbook/orders?market={market}" : "/orderbook/orders";
            return _dex.GetAsync<List<Order>>(path);
        }

        public Task<List<Order>> GetOrderHistoryAsync(int limit = 50)
            => _dex.GetAsync<List<Order>>($"/orderbook/orders/history?limit={limit}");
    }

    /// <summary>
    /// Farms sub-client
    /// </summary>
    public class FarmsClient
    {
        private readonly SynorDex _dex;
        internal FarmsClient(SynorDex dex) => _dex = dex;

        public Task<List<Farm>> ListFarmsAsync()
            => _dex.GetAsync<List<Farm>>("/farms");

        public Task<Farm> GetFarmAsync(string farmId)
            => _dex.GetAsync<Farm>($"/farms/{farmId}");

        public Task<FarmPosition> StakeAsync(StakeParams prms)
            => _dex.PostAsync<FarmPosition>("/farms/stake", new
            {
                farm = prms.Farm,
                amount = prms.Amount.ToString()
            });

        public Task<FarmPosition> UnstakeAsync(string farm, System.Numerics.BigInteger amount)
            => _dex.PostAsync<FarmPosition>("/farms/unstake", new
            {
                farm,
                amount = amount.ToString()
            });

        public Task<ClaimRewardsResult> ClaimRewardsAsync(string farm)
            => _dex.PostAsync<ClaimRewardsResult>("/farms/claim", new { farm });

        public Task<List<FarmPosition>> GetMyFarmPositionsAsync()
            => _dex.GetAsync<List<FarmPosition>>("/farms/positions");
    }
}
