import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'contract_types.dart';

/// Synor Contract SDK client for Flutter/Dart.
/// Smart contract deployment, interaction, and event handling.
class ContractClient {
  static const String version = '0.1.0';

  final ContractConfig config;
  final http.Client _client;
  bool _closed = false;

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

  // ==================== Contract Deployment ====================

  Future<DeploymentResult> deploy(DeployContractOptions options) async {
    final body = {
      'bytecode': options.bytecode,
      if (options.abi != null) 'abi': options.abi!.map((e) => e.toJson()).toList(),
      if (options.constructorArgs != null) 'constructor_args': options.constructorArgs,
      if (options.value != null) 'value': options.value,
      if (options.gasLimit != null) 'gas_limit': options.gasLimit,
      if (options.gasPrice != null) 'gas_price': options.gasPrice,
      if (options.nonce != null) 'nonce': options.nonce,
    };
    final response = await _post('/contract/deploy', body);
    return DeploymentResult.fromJson(response);
  }

  Future<DeploymentResult> deployCreate2(DeployContractOptions options, String salt) async {
    final body = {
      'bytecode': options.bytecode,
      'salt': salt,
      if (options.abi != null) 'abi': options.abi!.map((e) => e.toJson()).toList(),
      if (options.constructorArgs != null) 'constructor_args': options.constructorArgs,
      if (options.value != null) 'value': options.value,
      if (options.gasLimit != null) 'gas_limit': options.gasLimit,
      if (options.gasPrice != null) 'gas_price': options.gasPrice,
    };
    final response = await _post('/contract/deploy/create2', body);
    return DeploymentResult.fromJson(response);
  }

  Future<String> predictAddress(String bytecode, String salt, {String? deployer}) async {
    final body = {
      'bytecode': bytecode,
      'salt': salt,
      if (deployer != null) 'deployer': deployer,
    };
    final response = await _post('/contract/predict-address', body);
    return response['address'] as String;
  }

  // ==================== Contract Interaction ====================

  Future<dynamic> call(CallContractOptions options) async {
    final body = {
      'contract': options.contract,
      'method': options.method,
      'args': options.args,
      'abi': options.abi.map((e) => e.toJson()).toList(),
    };
    return await _post('/contract/call', body);
  }

  Future<TransactionResult> send(SendContractOptions options) async {
    final body = {
      'contract': options.contract,
      'method': options.method,
      'args': options.args,
      'abi': options.abi.map((e) => e.toJson()).toList(),
      if (options.value != null) 'value': options.value,
      if (options.gasLimit != null) 'gas_limit': options.gasLimit,
      if (options.gasPrice != null) 'gas_price': options.gasPrice,
      if (options.nonce != null) 'nonce': options.nonce,
    };
    final response = await _post('/contract/send', body);
    return TransactionResult.fromJson(response);
  }

  // ==================== Events ====================

  Future<List<DecodedEvent>> getEvents(EventFilter filter) async {
    final body = {
      'contract': filter.contract,
      if (filter.event != null) 'event': filter.event,
      if (filter.fromBlock != null) 'from_block': filter.fromBlock,
      if (filter.toBlock != null) 'to_block': filter.toBlock,
      if (filter.topics != null) 'topics': filter.topics,
      if (filter.abi != null) 'abi': filter.abi!.map((e) => e.toJson()).toList(),
    };
    final response = await _post('/contract/events', body);
    return (response as List).map((e) => DecodedEvent.fromJson(e)).toList();
  }

  Future<List<EventLog>> getLogs(String contract, {int? fromBlock, int? toBlock}) async {
    var path = '/contract/logs?contract=${Uri.encodeComponent(contract)}';
    if (fromBlock != null) path += '&from_block=$fromBlock';
    if (toBlock != null) path += '&to_block=$toBlock';
    final response = await _get(path);
    return (response as List).map((e) => EventLog.fromJson(e)).toList();
  }

  Future<List<DecodedEvent>> decodeLogs(List<EventLog> logs, List<AbiEntry> abi) async {
    final body = {
      'logs': logs.map((e) => e.toJson()).toList(),
      'abi': abi.map((e) => e.toJson()).toList(),
    };
    final response = await _post('/contract/decode-logs', body);
    return (response as List).map((e) => DecodedEvent.fromJson(e)).toList();
  }

  // ==================== ABI Utilities ====================

  Future<String> encodeCall(EncodeCallOptions options) async {
    final body = {
      'method': options.method,
      'args': options.args,
      'abi': options.abi.map((e) => e.toJson()).toList(),
    };
    final response = await _post('/contract/encode', body);
    return response['data'] as String;
  }

  Future<dynamic> decodeResult(DecodeResultOptions options) async {
    final body = {
      'data': options.data,
      'method': options.method,
      'abi': options.abi.map((e) => e.toJson()).toList(),
    };
    final response = await _post('/contract/decode', body);
    return response['result'];
  }

  Future<String> getSelector(String signature) async {
    final response = await _get('/contract/selector?signature=${Uri.encodeComponent(signature)}');
    return response['selector'] as String;
  }

  // ==================== Gas Estimation ====================

  Future<GasEstimation> estimateGas(EstimateGasOptions options) async {
    final body = {
      'contract': options.contract,
      'method': options.method,
      'args': options.args,
      'abi': options.abi.map((e) => e.toJson()).toList(),
      if (options.value != null) 'value': options.value,
    };
    final response = await _post('/contract/estimate-gas', body);
    return GasEstimation.fromJson(response);
  }

  // ==================== Contract Information ====================

  Future<BytecodeInfo> getBytecode(String address) async {
    final response = await _get('/contract/${Uri.encodeComponent(address)}/bytecode');
    return BytecodeInfo.fromJson(response);
  }

  Future<VerificationResult> verify(VerifyContractOptions options) async {
    final body = {
      'address': options.address,
      'source_code': options.sourceCode,
      'compiler_version': options.compilerVersion,
      if (options.constructorArgs != null) 'constructor_args': options.constructorArgs,
      if (options.optimization != null) 'optimization': options.optimization,
      if (options.optimizationRuns != null) 'optimization_runs': options.optimizationRuns,
      if (options.license != null) 'license': options.license,
    };
    final response = await _post('/contract/verify', body);
    return VerificationResult.fromJson(response);
  }

  Future<VerificationResult> getVerificationStatus(String address) async {
    final response = await _get('/contract/${Uri.encodeComponent(address)}/verification');
    return VerificationResult.fromJson(response);
  }

  // ==================== Multicall ====================

  Future<List<MulticallResult>> multicall(List<MulticallRequest> requests) async {
    final body = {
      'calls': requests.map((e) => e.toJson()).toList(),
    };
    final response = await _post('/contract/multicall', body);
    return (response as List).map((e) => MulticallResult.fromJson(e)).toList();
  }

  // ==================== Storage ====================

  Future<String> readStorage(ReadStorageOptions options) async {
    var path = '/contract/storage?contract=${Uri.encodeComponent(options.contract)}'
        '&slot=${Uri.encodeComponent(options.slot)}';
    if (options.blockNumber != null) path += '&block=${options.blockNumber}';
    final response = await _get(path);
    return response['value'] as String;
  }

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

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

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

  bool get isClosed => _closed;

  // ==================== HTTP Helpers ====================

  Future<dynamic> _get(String path) async {
    _checkClosed();
    return _executeWithRetry(() async {
      final response = await _client
          .get(
            Uri.parse('${config.endpoint}$path'),
            headers: _headers,
          )
          .timeout(Duration(milliseconds: config.timeoutMs));
      return _handleResponse(response);
    });
  }

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

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

  dynamic _handleResponse(http.Response response) {
    final body = jsonDecode(response.body);

    if (response.statusCode >= 200 && response.statusCode < 300) {
      return body;
    }

    final message = body['message'] ?? body['error'] ?? 'Unknown error';
    final code = body['code']?.toString();
    throw ContractException(
      message: message,
      statusCode: response.statusCode,
      code: code,
    );
  }

  Future<T> _executeWithRetry<T>(Future<T> Function() action) async {
    Object? lastError;
    for (var attempt = 0; attempt < config.retries; attempt++) {
      try {
        return await action();
      } catch (e) {
        lastError = e;
        if (attempt < config.retries - 1) {
          await Future.delayed(Duration(seconds: 1 << attempt));
        }
      }
    }
    throw lastError ?? ContractException(message: 'Request failed');
  }

  void _checkClosed() {
    if (_closed) {
      throw ContractException(message: 'Client has been closed');
    }
  }
}

/// Contract SDK configuration.
class ContractConfig {
  final String apiKey;
  final String endpoint;
  final int timeoutMs;
  final int retries;

  ContractConfig({
    required this.apiKey,
    this.endpoint = 'https://contract.synor.io',
    this.timeoutMs = 30000,
    this.retries = 3,
  });
}

/// Contract SDK exception.
class ContractException implements Exception {
  final String message;
  final int? statusCode;
  final String? code;

  ContractException({required this.message, this.statusCode, this.code});

  @override
  String toString() => 'ContractException: $message (code: $code, status: $statusCode)';
}
