/// Synor Bridge SDK Client for Flutter
///
/// Cross-chain asset transfers with lock-mint and burn-unlock patterns.
library synor_bridge;

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

export 'types.dart';

/// Synor Bridge Client
class SynorBridge {
  final BridgeConfig config;
  final http.Client _client;
  bool _closed = false;

  static const _finalStatuses = {
    TransferStatus.completed,
    TransferStatus.failed,
    TransferStatus.refunded,
  };

  SynorBridge(this.config) : _client = http.Client();

  // ==================== Chain Operations ====================

  Future<List<Chain>> getSupportedChains() async {
    final resp = await _request('GET', '/chains');
    return (resp['chains'] as List).map((e) => Chain.fromJson(e)).toList();
  }

  Future<Chain> getChain(ChainId chainId) async {
    final resp = await _request('GET', '/chains/${chainId.name}');
    return Chain.fromJson(resp);
  }

  Future<bool> isChainSupported(ChainId chainId) async {
    try {
      final chain = await getChain(chainId);
      return chain.supported;
    } catch (_) {
      return false;
    }
  }

  // ==================== Asset Operations ====================

  Future<List<Asset>> getSupportedAssets(ChainId chainId) async {
    final resp = await _request('GET', '/chains/${chainId.name}/assets');
    return (resp['assets'] as List).map((e) => Asset.fromJson(e)).toList();
  }

  Future<Asset> getAsset(String assetId) async {
    final resp = await _request('GET', '/assets/${Uri.encodeComponent(assetId)}');
    return Asset.fromJson(resp);
  }

  Future<WrappedAsset> getWrappedAsset(String originalAssetId, ChainId targetChain) async {
    final path = '/assets/${Uri.encodeComponent(originalAssetId)}/wrapped/${targetChain.name}';
    final resp = await _request('GET', path);
    return WrappedAsset.fromJson(resp);
  }

  // ==================== Fee & Rate Operations ====================

  Future<FeeEstimate> estimateFee(String asset, String amount, ChainId sourceChain, ChainId targetChain) async {
    final resp = await _request('POST', '/fees/estimate', {
      'asset': asset,
      'amount': amount,
      'sourceChain': sourceChain.name,
      'targetChain': targetChain.name,
    });
    return FeeEstimate.fromJson(resp);
  }

  Future<ExchangeRate> getExchangeRate(String fromAsset, String toAsset) async {
    final path = '/rates/${Uri.encodeComponent(fromAsset)}/${Uri.encodeComponent(toAsset)}';
    final resp = await _request('GET', path);
    return ExchangeRate.fromJson(resp);
  }

  // ==================== Lock-Mint Flow ====================

  Future<LockReceipt> lock(String asset, String amount, ChainId targetChain, {LockOptions? options}) async {
    final body = <String, dynamic>{
      'asset': asset,
      'amount': amount,
      'targetChain': targetChain.name,
    };
    if (options != null) body.addAll(options.toJson());
    final resp = await _request('POST', '/transfers/lock', body);
    return LockReceipt.fromJson(resp);
  }

  Future<LockProof> getLockProof(String lockReceiptId) async {
    final resp = await _request('GET', '/transfers/lock/${Uri.encodeComponent(lockReceiptId)}/proof');
    return LockProof.fromJson(resp);
  }

  Future<LockProof> waitForLockProof(
    String lockReceiptId, {
    Duration pollInterval = const Duration(seconds: 5),
    Duration maxWait = const Duration(minutes: 10),
  }) async {
    final deadline = DateTime.now().add(maxWait);
    while (DateTime.now().isBefore(deadline)) {
      try {
        return await getLockProof(lockReceiptId);
      } on BridgeException catch (e) {
        if (e.isConfirmationsPending) {
          await Future.delayed(pollInterval);
          continue;
        }
        rethrow;
      }
    }
    throw const BridgeException('Timeout waiting for lock proof', code: 'CONFIRMATIONS_PENDING');
  }

  Future<SignedTransaction> mint(LockProof proof, String targetAddress, {MintOptions? options}) async {
    final body = <String, dynamic>{
      'proof': proof.toJson(),
      'targetAddress': targetAddress,
    };
    if (options != null) body.addAll(options.toJson());
    final resp = await _request('POST', '/transfers/mint', body);
    return SignedTransaction.fromJson(resp);
  }

  // ==================== Burn-Unlock Flow ====================

  Future<BurnReceipt> burn(String wrappedAsset, String amount, {BurnOptions? options}) async {
    final body = <String, dynamic>{
      'wrappedAsset': wrappedAsset,
      'amount': amount,
    };
    if (options != null) body.addAll(options.toJson());
    final resp = await _request('POST', '/transfers/burn', body);
    return BurnReceipt.fromJson(resp);
  }

  Future<BurnProof> getBurnProof(String burnReceiptId) async {
    final resp = await _request('GET', '/transfers/burn/${Uri.encodeComponent(burnReceiptId)}/proof');
    return BurnProof.fromJson(resp);
  }

  Future<BurnProof> waitForBurnProof(
    String burnReceiptId, {
    Duration pollInterval = const Duration(seconds: 5),
    Duration maxWait = const Duration(minutes: 10),
  }) async {
    final deadline = DateTime.now().add(maxWait);
    while (DateTime.now().isBefore(deadline)) {
      try {
        return await getBurnProof(burnReceiptId);
      } on BridgeException catch (e) {
        if (e.isConfirmationsPending) {
          await Future.delayed(pollInterval);
          continue;
        }
        rethrow;
      }
    }
    throw const BridgeException('Timeout waiting for burn proof', code: 'CONFIRMATIONS_PENDING');
  }

  Future<SignedTransaction> unlock(BurnProof proof, {UnlockOptions? options}) async {
    final body = <String, dynamic>{'proof': proof.toJson()};
    if (options != null) body.addAll(options.toJson());
    final resp = await _request('POST', '/transfers/unlock', body);
    return SignedTransaction.fromJson(resp);
  }

  // ==================== Transfer Management ====================

  Future<Transfer> getTransfer(String transferId) async {
    final resp = await _request('GET', '/transfers/${Uri.encodeComponent(transferId)}');
    return Transfer.fromJson(resp);
  }

  Future<TransferStatus> getTransferStatus(String transferId) async {
    final transfer = await getTransfer(transferId);
    return transfer.status;
  }

  Future<List<Transfer>> listTransfers({TransferFilter? filter}) async {
    var path = '/transfers';
    final params = <String>[];
    if (filter?.status != null) params.add('status=${filter!.status!.name}');
    if (filter?.sourceChain != null) params.add('sourceChain=${filter!.sourceChain!.name}');
    if (filter?.targetChain != null) params.add('targetChain=${filter!.targetChain!.name}');
    if (filter?.limit != null) params.add('limit=${filter!.limit}');
    if (filter?.offset != null) params.add('offset=${filter!.offset}');
    if (params.isNotEmpty) path = '$path?${params.join('&')}';

    final resp = await _request('GET', path);
    return (resp['transfers'] as List).map((e) => Transfer.fromJson(e)).toList();
  }

  Future<Transfer> waitForTransfer(
    String transferId, {
    Duration pollInterval = const Duration(seconds: 10),
    Duration maxWait = const Duration(minutes: 30),
  }) async {
    final deadline = DateTime.now().add(maxWait);
    while (DateTime.now().isBefore(deadline)) {
      final transfer = await getTransfer(transferId);
      if (_finalStatuses.contains(transfer.status)) {
        return transfer;
      }
      await Future.delayed(pollInterval);
    }
    throw const BridgeException('Timeout waiting for transfer completion');
  }

  // ==================== Convenience Methods ====================

  Future<Transfer> bridgeTo(
    String asset,
    String amount,
    ChainId targetChain,
    String targetAddress, {
    LockOptions? lockOptions,
    MintOptions? mintOptions,
  }) async {
    final lockReceipt = await lock(asset, amount, targetChain, options: lockOptions);
    if (config.debug) print('Locked: ${lockReceipt.id}, waiting for confirmations...');

    final proof = await waitForLockProof(lockReceipt.id);
    if (config.debug) print('Proof ready, minting on ${targetChain.name}...');

    await mint(proof, targetAddress, options: mintOptions);
    return waitForTransfer(lockReceipt.id);
  }

  Future<Transfer> bridgeBack(
    String wrappedAsset,
    String amount, {
    BurnOptions? burnOptions,
    UnlockOptions? unlockOptions,
  }) async {
    final burnReceipt = await burn(wrappedAsset, amount, options: burnOptions);
    if (config.debug) print('Burned: ${burnReceipt.id}, waiting for confirmations...');

    final proof = await waitForBurnProof(burnReceipt.id);
    if (config.debug) print('Proof ready, unlocking on ${burnReceipt.targetChain.name}...');

    await unlock(proof, options: unlockOptions);
    return waitForTransfer(burnReceipt.id);
  }

  // ==================== Lifecycle ====================

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

  bool get isClosed => _closed;

  Future<bool> healthCheck() async {
    try {
      final resp = await _request('GET', '/health');
      return resp['status'] == 'healthy';
    } catch (_) {
      return false;
    }
  }

  // ==================== Private Methods ====================

  Future<Map<String, dynamic>> _request(String method, String path, [Map<String, dynamic>? body]) async {
    if (_closed) throw const BridgeException('Client has been closed');

    Exception? lastError;
    for (var attempt = 0; attempt < config.retries; attempt++) {
      try {
        return await _doRequest(method, path, body);
      } catch (e) {
        lastError = e as Exception;
        if (attempt < config.retries - 1) {
          await Future.delayed(Duration(seconds: 1 << attempt));
        }
      }
    }
    throw lastError ?? const BridgeException('Unknown error');
  }

  Future<Map<String, dynamic>> _doRequest(String method, String path, Map<String, dynamic>? body) async {
    final uri = Uri.parse('${config.endpoint}$path');
    final headers = {
      'Authorization': 'Bearer ${config.apiKey}',
      'Content-Type': 'application/json',
      'X-SDK-Version': 'flutter/0.1.0',
    };

    http.Response response;
    switch (method) {
      case 'GET':
        response = await _client.get(uri, headers: headers);
        break;
      case 'POST':
        response = await _client.post(uri, headers: headers, body: body != null ? jsonEncode(body) : null);
        break;
      case 'DELETE':
        response = await _client.delete(uri, headers: headers);
        break;
      default:
        throw BridgeException('Unknown method: $method');
    }

    if (response.statusCode >= 400) {
      final error = jsonDecode(response.body) as Map<String, dynamic>;
      throw BridgeException(
        error['message'] as String? ?? 'HTTP ${response.statusCode}',
        code: error['code'] as String?,
        statusCode: response.statusCode,
      );
    }

    if (response.body.isEmpty) return {};
    return jsonDecode(response.body) as Map<String, dynamic>;
  }
}
