/// Synor DEX SDK for Flutter/Dart
///
/// 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
library synor_dex;

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'types.dart';

export 'types.dart';

/// Main DEX client
class SynorDex {
  final DexConfig config;
  final http.Client _client;
  bool _closed = false;

  late final PerpsClient perps;
  late final OrderBookClient orderbook;
  late final FarmsClient farms;

  SynorDex(this.config) : _client = http.Client() {
    perps = PerpsClient(this);
    orderbook = OrderBookClient(this);
    farms = FarmsClient(this);
  }

  // Token Operations

  Future<Token> getToken(String address) async {
    return _get('/tokens/$address', Token.fromJson);
  }

  Future<List<Token>> listTokens() async {
    return _getList('/tokens', Token.fromJson);
  }

  Future<List<Token>> searchTokens(String query) async {
    return _getList('/tokens/search?q=${Uri.encodeComponent(query)}', Token.fromJson);
  }

  // Pool Operations

  Future<Pool> getPool(String tokenA, String tokenB) async {
    return _get('/pools/$tokenA/$tokenB', Pool.fromJson);
  }

  Future<Pool> getPoolById(String poolId) async {
    return _get('/pools/$poolId', Pool.fromJson);
  }

  Future<List<Pool>> listPools({PoolFilter? filter}) async {
    final params = <String>[];
    if (filter != null) {
      if (filter.tokens != null) params.add('tokens=${filter.tokens!.join(",")}');
      if (filter.minTvl != null) params.add('min_tvl=${filter.minTvl}');
      if (filter.minVolume24h != null) params.add('min_volume=${filter.minVolume24h}');
      if (filter.verified != null) params.add('verified=${filter.verified}');
      if (filter.limit != null) params.add('limit=${filter.limit}');
      if (filter.offset != null) params.add('offset=${filter.offset}');
    }
    final path = params.isEmpty ? '/pools' : '/pools?${params.join("&")}';
    return _getList(path, Pool.fromJson);
  }

  // Swap Operations

  Future<Quote> getQuote(QuoteParams params) async {
    return _post('/swap/quote', {
      'token_in': params.tokenIn,
      'token_out': params.tokenOut,
      'amount_in': params.amountIn.toString(),
      'slippage': params.slippage ?? 0.005,
    }, Quote.fromJson);
  }

  Future<SwapResult> swap(SwapParams params) async {
    final deadline = params.deadline ??
        (DateTime.now().millisecondsSinceEpoch ~/ 1000) + 1200;
    return _post('/swap', {
      'token_in': params.tokenIn,
      'token_out': params.tokenOut,
      'amount_in': params.amountIn.toString(),
      'min_amount_out': params.minAmountOut.toString(),
      'deadline': deadline,
      if (params.recipient != null) 'recipient': params.recipient,
    }, SwapResult.fromJson);
  }

  // Liquidity Operations

  Future<LiquidityResult> addLiquidity(AddLiquidityParams params) async {
    final deadline = params.deadline ??
        (DateTime.now().millisecondsSinceEpoch ~/ 1000) + 1200;
    return _post('/liquidity/add', {
      'token_a': params.tokenA,
      'token_b': params.tokenB,
      'amount_a': params.amountA.toString(),
      'amount_b': params.amountB.toString(),
      'deadline': deadline,
      if (params.minAmountA != null) 'min_amount_a': params.minAmountA.toString(),
      if (params.minAmountB != null) 'min_amount_b': params.minAmountB.toString(),
    }, LiquidityResult.fromJson);
  }

  Future<LiquidityResult> removeLiquidity(RemoveLiquidityParams params) async {
    final deadline = params.deadline ??
        (DateTime.now().millisecondsSinceEpoch ~/ 1000) + 1200;
    return _post('/liquidity/remove', {
      'pool': params.pool,
      'lp_amount': params.lpAmount.toString(),
      'deadline': deadline,
      if (params.minAmountA != null) 'min_amount_a': params.minAmountA.toString(),
      if (params.minAmountB != null) 'min_amount_b': params.minAmountB.toString(),
    }, LiquidityResult.fromJson);
  }

  Future<List<LPPosition>> getMyPositions() async {
    return _getList('/liquidity/positions', LPPosition.fromJson);
  }

  // Analytics

  Future<List<OHLCV>> getPriceHistory(String pair, String interval, {int limit = 100}) async {
    return _getList('/analytics/candles/$pair?interval=$interval&limit=$limit', OHLCV.fromJson);
  }

  Future<List<TradeHistory>> getTradeHistory(String pair, {int limit = 50}) async {
    return _getList('/analytics/trades/$pair?limit=$limit', TradeHistory.fromJson);
  }

  Future<VolumeStats> getVolumeStats() async {
    return _get('/analytics/volume', VolumeStats.fromJson);
  }

  Future<TVLStats> getTVL() async {
    return _get('/analytics/tvl', TVLStats.fromJson);
  }

  // Lifecycle

  Future<bool> healthCheck() async {
    try {
      final response = await _get('/health', (json) => json['status'] as String);
      return response == 'healthy';
    } catch (_) {
      return false;
    }
  }

  void close() {
    _closed = true;
    _client.close();
  }

  bool get isClosed => _closed;

  // Internal methods

  Future<T> _get<T>(String path, T Function(Map<String, dynamic>) fromJson) async {
    _checkClosed();
    final response = await _client.get(
      Uri.parse('${config.endpoint}$path'),
      headers: _headers,
    );
    return _handleResponse(response, fromJson);
  }

  Future<List<T>> _getList<T>(String path, T Function(Map<String, dynamic>) fromJson) async {
    _checkClosed();
    final response = await _client.get(
      Uri.parse('${config.endpoint}$path'),
      headers: _headers,
    );
    _checkResponse(response);
    final list = jsonDecode(response.body) as List;
    return list.map((e) => fromJson(e as Map<String, dynamic>)).toList();
  }

  Future<T> _post<T>(String path, Map<String, dynamic> body, T Function(Map<String, dynamic>) fromJson) async {
    _checkClosed();
    final response = await _client.post(
      Uri.parse('${config.endpoint}$path'),
      headers: _headers,
      body: jsonEncode(body),
    );
    return _handleResponse(response, fromJson);
  }

  Future<T> _delete<T>(String path, T Function(Map<String, dynamic>) fromJson) async {
    _checkClosed();
    final response = await _client.delete(
      Uri.parse('${config.endpoint}$path'),
      headers: _headers,
    );
    return _handleResponse(response, fromJson);
  }

  Map<String, String> get _headers => {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ${config.apiKey}',
    'X-SDK-Version': 'flutter/0.1.0',
  };

  T _handleResponse<T>(http.Response response, T Function(Map<String, dynamic>) fromJson) {
    _checkResponse(response);
    final json = jsonDecode(response.body) as Map<String, dynamic>;
    return fromJson(json);
  }

  void _checkResponse(http.Response response) {
    if (response.statusCode >= 400) {
      Map<String, dynamic>? error;
      try {
        error = jsonDecode(response.body) as Map<String, dynamic>;
      } catch (_) {}
      throw DexException(
        error?['message'] ?? 'HTTP ${response.statusCode}',
        error?['code'],
        response.statusCode,
      );
    }
  }

  void _checkClosed() {
    if (_closed) {
      throw DexException('Client has been closed', 'CLIENT_CLOSED', 0);
    }
  }
}

/// Perpetual futures sub-client
class PerpsClient {
  final SynorDex _dex;
  PerpsClient(this._dex);

  Future<List<PerpMarket>> listMarkets() async {
    return _dex._getList('/perps/markets', PerpMarket.fromJson);
  }

  Future<PerpMarket> getMarket(String symbol) async {
    return _dex._get('/perps/markets/$symbol', PerpMarket.fromJson);
  }

  Future<PerpPosition> openPosition(OpenPositionParams params) async {
    return _dex._post('/perps/positions', {
      'market': params.market,
      'side': params.side.name,
      'size': params.size.toString(),
      'leverage': params.leverage,
      'order_type': params.orderType.name,
      'margin_type': params.marginType.name,
      'reduce_only': params.reduceOnly,
      if (params.limitPrice != null) 'limit_price': params.limitPrice,
      if (params.stopLoss != null) 'stop_loss': params.stopLoss,
      if (params.takeProfit != null) 'take_profit': params.takeProfit,
    }, PerpPosition.fromJson);
  }

  Future<PerpPosition> closePosition(ClosePositionParams params) async {
    return _dex._post('/perps/positions/close', {
      'market': params.market,
      'order_type': params.orderType.name,
      if (params.size != null) 'size': params.size.toString(),
      if (params.limitPrice != null) 'limit_price': params.limitPrice,
    }, PerpPosition.fromJson);
  }

  Future<List<PerpPosition>> getPositions() async {
    return _dex._getList('/perps/positions', PerpPosition.fromJson);
  }

  Future<List<PerpOrder>> getOrders() async {
    return _dex._getList('/perps/orders', PerpOrder.fromJson);
  }

  Future<List<FundingPayment>> getFundingHistory(String market, {int limit = 100}) async {
    return _dex._getList('/perps/funding/$market?limit=$limit', FundingPayment.fromJson);
  }

  Future<FundingRateInfo> getFundingRate(String market) async {
    return _dex._get('/perps/funding/$market/current', FundingRateInfo.fromJson);
  }
}

/// Order book sub-client
class OrderBookClient {
  final SynorDex _dex;
  OrderBookClient(this._dex);

  Future<OrderBook> getOrderBook(String market, {int depth = 20}) async {
    return _dex._get('/orderbook/$market?depth=$depth', OrderBook.fromJson);
  }

  Future<Order> placeLimitOrder(LimitOrderParams params) async {
    return _dex._post('/orderbook/orders', {
      'market': params.market,
      'side': params.side,
      'price': params.price,
      'size': params.size.toString(),
      'time_in_force': params.timeInForce.name,
      'post_only': params.postOnly,
    }, Order.fromJson);
  }

  Future<List<Order>> getOpenOrders({String? market}) async {
    final path = market != null ? '/orderbook/orders?market=$market' : '/orderbook/orders';
    return _dex._getList(path, Order.fromJson);
  }

  Future<List<Order>> getOrderHistory({int limit = 50}) async {
    return _dex._getList('/orderbook/orders/history?limit=$limit', Order.fromJson);
  }
}

/// Farms sub-client
class FarmsClient {
  final SynorDex _dex;
  FarmsClient(this._dex);

  Future<List<Farm>> listFarms() async {
    return _dex._getList('/farms', Farm.fromJson);
  }

  Future<Farm> getFarm(String farmId) async {
    return _dex._get('/farms/$farmId', Farm.fromJson);
  }

  Future<FarmPosition> stake(StakeParams params) async {
    return _dex._post('/farms/stake', {
      'farm': params.farm,
      'amount': params.amount.toString(),
    }, FarmPosition.fromJson);
  }

  Future<FarmPosition> unstake(String farm, BigInt amount) async {
    return _dex._post('/farms/unstake', {
      'farm': farm,
      'amount': amount.toString(),
    }, FarmPosition.fromJson);
  }

  Future<ClaimRewardsResult> claimRewards(String farm) async {
    return _dex._post('/farms/claim', {'farm': farm}, ClaimRewardsResult.fromJson);
  }

  Future<List<FarmPosition>> getMyFarmPositions() async {
    return _dex._getList('/farms/positions', FarmPosition.fromJson);
  }
}
