/// Synor IBC SDK for Flutter/Dart
///
/// Inter-Blockchain Communication (IBC) protocol for cross-chain interoperability.
library synor_ibc;

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

export 'types.dart';

/// Main IBC client
class SynorIbc {
  final IbcConfig config;
  final http.Client _client;
  bool _closed = false;

  late final LightClientClient clients;
  late final ConnectionsClient connections;
  late final ChannelsClient channels;
  late final TransferClient transfer;
  late final SwapsClient swaps;

  SynorIbc(this.config) : _client = http.Client() {
    clients = LightClientClient(this);
    connections = ConnectionsClient(this);
    channels = ChannelsClient(this);
    transfer = TransferClient(this);
    swaps = SwapsClient(this);
  }

  String get chainId => config.chainId;

  Future<Map<String, dynamic>> getChainInfo() async {
    return _get('/chain');
  }

  Future<Height> getHeight() async {
    final result = await _get('/chain/height');
    return Height.fromJson(result);
  }

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

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

  bool get isClosed => _closed;

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

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

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

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

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

/// Light client sub-client
class LightClientClient {
  final SynorIbc _ibc;
  LightClientClient(this._ibc);

  Future<ClientId> create({
    required ClientType clientType,
    required ClientState clientState,
    required Map<String, dynamic> consensusState,
  }) async {
    final result = await _ibc._post('/clients', {
      'client_type': clientType.name,
      'client_state': clientState.toJson(),
      'consensus_state': consensusState,
    });
    return ClientId(result['client_id']);
  }

  Future<ClientState> getState(ClientId clientId) async {
    final result = await _ibc._get('/clients/${clientId.id}/state');
    return ClientState.fromJson(result);
  }

  Future<List<Map<String, dynamic>>> list() async {
    final result = await _ibc._get('/clients');
    return List<Map<String, dynamic>>.from(result['clients'] ?? []);
  }
}

/// Connections sub-client
class ConnectionsClient {
  final SynorIbc _ibc;
  ConnectionsClient(this._ibc);

  Future<ConnectionId> openInit({
    required ClientId clientId,
    required ClientId counterpartyClientId,
  }) async {
    final result = await _ibc._post('/connections/init', {
      'client_id': clientId.id,
      'counterparty_client_id': counterpartyClientId.id,
    });
    return ConnectionId(result['connection_id']);
  }

  Future<Map<String, dynamic>> get(ConnectionId connectionId) async {
    return _ibc._get('/connections/${connectionId.id}');
  }

  Future<List<Map<String, dynamic>>> list() async {
    final result = await _ibc._get('/connections');
    return List<Map<String, dynamic>>.from(result['connections'] ?? []);
  }
}

/// Channels sub-client
class ChannelsClient {
  final SynorIbc _ibc;
  ChannelsClient(this._ibc);

  Future<void> bindPort(PortId portId, String module) async {
    await _ibc._post('/ports/bind', {'port_id': portId.id, 'module': module});
  }

  Future<ChannelId> openInit({
    required PortId portId,
    required ChannelOrder ordering,
    required ConnectionId connectionId,
    required PortId counterpartyPort,
    required String version,
  }) async {
    final result = await _ibc._post('/channels/init', {
      'port_id': portId.id,
      'ordering': ordering.name,
      'connection_id': connectionId.id,
      'counterparty_port': counterpartyPort.id,
      'version': version,
    });
    return ChannelId(result['channel_id']);
  }

  Future<Map<String, dynamic>> get(PortId portId, ChannelId channelId) async {
    return _ibc._get('/channels/${portId.id}/${channelId.id}');
  }

  Future<List<Map<String, dynamic>>> list() async {
    final result = await _ibc._get('/channels');
    return List<Map<String, dynamic>>.from(result['channels'] ?? []);
  }
}

/// Transfer sub-client (ICS-20)
class TransferClient {
  final SynorIbc _ibc;
  TransferClient(this._ibc);

  Future<Map<String, dynamic>> transfer({
    required String sourcePort,
    required String sourceChannel,
    required String denom,
    required String amount,
    required String sender,
    required String receiver,
    Timeout? timeout,
    String? memo,
  }) async {
    final body = <String, dynamic>{
      'source_port': sourcePort,
      'source_channel': sourceChannel,
      'token': {'denom': denom, 'amount': amount},
      'sender': sender,
      'receiver': receiver,
    };
    if (timeout != null) {
      body['timeout_height'] = timeout.height.toJson();
      body['timeout_timestamp'] = timeout.timestamp.toString();
    }
    if (memo != null) body['memo'] = memo;
    return _ibc._post('/transfer', body);
  }

  Future<Map<String, dynamic>> getDenomTrace(String ibcDenom) async {
    return _ibc._get('/transfer/denom_trace/$ibcDenom');
  }
}

/// Swaps sub-client (HTLC)
class SwapsClient {
  final SynorIbc _ibc;
  SwapsClient(this._ibc);

  Future<Map<String, dynamic>> initiate({
    required String responder,
    required Map<String, dynamic> initiatorAsset,
    required Map<String, dynamic> responderAsset,
  }) async {
    return _ibc._post('/swaps/initiate', {
      'responder': responder,
      'initiator_asset': initiatorAsset,
      'responder_asset': responderAsset,
    });
  }

  Future<void> lock(SwapId swapId) async {
    await _ibc._post('/swaps/${swapId.id}/lock', {});
  }

  Future<Map<String, dynamic>> respond(SwapId swapId, Map<String, dynamic> asset) async {
    return _ibc._post('/swaps/${swapId.id}/respond', {'asset': asset});
  }

  Future<Map<String, dynamic>> claim(SwapId swapId, List<int> secret) async {
    return _ibc._post('/swaps/${swapId.id}/claim', {
      'secret': base64Encode(secret),
    });
  }

  Future<Map<String, dynamic>> refund(SwapId swapId) async {
    return _ibc._post('/swaps/${swapId.id}/refund', {});
  }

  Future<AtomicSwap> get(SwapId swapId) async {
    final result = await _ibc._get('/swaps/${swapId.id}');
    return AtomicSwap.fromJson(result);
  }

  Future<List<AtomicSwap>> listActive() async {
    final result = await _ibc._get('/swaps/active');
    return (result['swaps'] as List).map((e) => AtomicSwap.fromJson(e)).toList();
  }
}
