/// Synor ZK SDK Client for Flutter/Dart
///
/// Zero-Knowledge proof systems for ZK-Rollups and privacy.
library synor_zk_client;

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

export 'types.dart';

/// Synor ZK SDK Client
///
/// Provides zero-knowledge proof generation and verification for ZK-Rollups.
///
/// Example:
/// ```dart
/// final zk = SynorZk(ZkConfig(apiKey: 'your-api-key'));
///
/// // Generate a proof
/// final proof = await zk.proofs.generate(ProofRequest(
///   circuitType: CircuitType.transfer,
///   publicInputs: [...],
///   privateInputs: [...],
/// ));
///
/// // Verify the proof
/// final result = await zk.proofs.verify(proof);
/// print('Valid: ${result.valid}');
/// ```
class SynorZk {
  final ZkConfig config;
  final http.Client _client;
  bool _closed = false;

  late final ProofsClient proofs;
  late final CircuitsClient circuits;
  late final RollupClient rollup;
  late final StateClient state;
  late final CeremonyClient ceremony;

  SynorZk(this.config, {http.Client? client})
      : _client = client ?? http.Client() {
    proofs = ProofsClient(this);
    circuits = CircuitsClient(this);
    rollup = RollupClient(this);
    state = StateClient(this);
    ceremony = CeremonyClient(this);
  }

  /// Get the default proof system
  ProofSystem get defaultProofSystem => config.defaultProofSystem;

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

  /// Get service info
  Future<Map<String, dynamic>> getInfo() async {
    return _get('/info');
  }

  /// Close the client
  void close() {
    _closed = true;
    _client.close();
  }

  /// Check if client is closed
  bool get isClosed => _closed;

  // Internal HTTP methods
  Future<Map<String, dynamic>> _get(String path) async {
    return _request('GET', path);
  }

  Future<Map<String, dynamic>> _post(String path, [Map<String, dynamic>? body]) async {
    return _request('POST', path, body);
  }

  Future<Map<String, dynamic>> _delete(String path) async {
    return _request('DELETE', path);
  }

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

    final url = Uri.parse('${config.endpoint}$path');
    final headers = {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ${config.apiKey}',
      'X-SDK-Version': 'flutter/0.1.0',
    };

    Exception? lastError;

    for (var attempt = 0; attempt <= config.retries; attempt++) {
      try {
        http.Response response;
        switch (method) {
          case 'GET':
            response = await _client
                .get(url, headers: headers)
                .timeout(config.timeout);
            break;
          case 'POST':
            response = await _client
                .post(url, headers: headers, body: body != null ? jsonEncode(body) : null)
                .timeout(config.timeout);
            break;
          case 'DELETE':
            response = await _client
                .delete(url, headers: headers)
                .timeout(config.timeout);
            break;
          default:
            throw ArgumentError('Unknown method: $method');
        }

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

        return jsonDecode(response.body) as Map<String, dynamic>;
      } on ZkError {
        rethrow;
      } catch (e) {
        lastError = e as Exception;
        if (attempt < config.retries) {
          await Future.delayed(Duration(milliseconds: 100 * (1 << attempt)));
        }
      }
    }

    throw ZkError(
      lastError?.toString() ?? 'Request failed',
      code: 'NETWORK_ERROR',
    );
  }
}

/// Proofs sub-client
class ProofsClient {
  final SynorZk _zk;

  ProofsClient(this._zk);

  /// Generate a zero-knowledge proof
  Future<Proof> generate(ProofRequest request) async {
    final body = request.toJson();
    body['system'] ??= _zk.defaultProofSystem.value;
    final result = await _zk._post('/proofs/generate', body);
    return Proof.fromJson(result);
  }

  /// Verify a proof
  Future<VerificationResult> verify(Proof proof) async {
    final result = await _zk._post('/proofs/verify', {
      'proof_id': proof.id,
      'data': proof.data,
      'public_inputs': proof.publicInputs,
      'system': proof.system.value,
      'circuit_type': proof.circuitType.value,
    });
    return VerificationResult.fromJson(result);
  }

  /// Get a proof by ID
  Future<Proof> get(String proofId) async {
    final result = await _zk._get('/proofs/$proofId');
    return Proof.fromJson(result);
  }

  /// List recent proofs
  Future<List<Proof>> list({int? limit, int? offset}) async {
    var path = '/proofs';
    final params = <String>[];
    if (limit != null) params.add('limit=$limit');
    if (offset != null) params.add('offset=$offset');
    if (params.isNotEmpty) path += '?${params.join('&')}';

    final result = await _zk._get(path);
    final proofs = result['proofs'] as List?;
    return proofs?.map((p) => Proof.fromJson(p as Map<String, dynamic>)).toList() ?? [];
  }

  /// Serialize a proof to bytes
  Future<Uint8List> serialize(Proof proof) async {
    final result = await _zk._post('/proofs/serialize', {'proof_id': proof.id});
    return base64Decode(result['data'] as String);
  }

  /// Deserialize bytes to a proof
  Future<Proof> deserialize(Uint8List data, ProofSystem system) async {
    final result = await _zk._post('/proofs/deserialize', {
      'data': base64Encode(data),
      'system': system.value,
    });
    return Proof.fromJson(result);
  }
}

/// Circuits sub-client
class CircuitsClient {
  final SynorZk _zk;

  CircuitsClient(this._zk);

  /// Get verification key for a circuit
  Future<VerificationKey> getVerificationKey(
    CircuitType circuitType, [
    ProofSystem? system,
  ]) async {
    final sys = system ?? _zk.defaultProofSystem;
    final result = await _zk._get('/circuits/${circuitType.value}/vk?system=${sys.value}');
    return VerificationKey.fromJson(result);
  }

  /// Get proving key for a circuit
  Future<ProvingKey> getProvingKey(
    CircuitType circuitType, [
    ProofSystem? system,
  ]) async {
    final sys = system ?? _zk.defaultProofSystem;
    final result = await _zk._get('/circuits/${circuitType.value}/pk?system=${sys.value}');
    return ProvingKey.fromJson(result);
  }

  /// List available circuits
  Future<List<CircuitConfig>> list() async {
    final result = await _zk._get('/circuits');
    final circuits = result['circuits'] as List?;
    return circuits?.map((c) => CircuitConfig.fromJson(c as Map<String, dynamic>)).toList() ?? [];
  }

  /// Get circuit configuration
  Future<CircuitConfig> getConfig(CircuitType circuitType) async {
    final result = await _zk._get('/circuits/${circuitType.value}/config');
    return CircuitConfig.fromJson(result);
  }

  /// Compile a custom circuit
  Future<String> compile(CircuitConfig config) async {
    final result = await _zk._post('/circuits/compile', config.toJson());
    return result['circuit_id'] as String;
  }
}

/// Rollup sub-client
class RollupClient {
  final SynorZk _zk;

  RollupClient(this._zk);

  /// Get rollup statistics
  Future<RollupStats> getStats() async {
    final result = await _zk._get('/rollup/stats');
    return RollupStats.fromJson(result);
  }

  /// Get current batch
  Future<Batch> getCurrentBatch() async {
    final result = await _zk._get('/rollup/batch/current');
    return Batch.fromJson(result);
  }

  /// Get batch by number
  Future<Batch> getBatch(int batchNumber) async {
    final result = await _zk._get('/rollup/batch/$batchNumber');
    return Batch.fromJson(result);
  }

  /// Submit a transfer
  Future<String> submitTransfer(Transfer transfer) async {
    final result = await _zk._post('/rollup/transfer', transfer.toJson());
    return result['tx_id'] as String;
  }

  /// Submit a deposit
  Future<String> submitDeposit(Deposit deposit) async {
    final result = await _zk._post('/rollup/deposit', deposit.toJson());
    return result['tx_id'] as String;
  }

  /// Submit a withdrawal
  Future<String> submitWithdrawal(Withdrawal withdrawal) async {
    final result = await _zk._post('/rollup/withdraw', withdrawal.toJson());
    return result['tx_id'] as String;
  }

  /// Finalize current batch
  Future<Batch> finalizeBatch() async {
    final result = await _zk._post('/rollup/batch/finalize', {});
    return Batch.fromJson(result);
  }

  /// Get pending transactions
  Future<List<Transfer>> getPendingTransactions() async {
    final result = await _zk._get('/rollup/pending');
    final transactions = result['transactions'] as List?;
    return transactions?.map((t) => Transfer.fromJson(t as Map<String, dynamic>)).toList() ?? [];
  }
}

/// State sub-client
class StateClient {
  final SynorZk _zk;

  StateClient(this._zk);

  /// Get current state root
  Future<String> getRoot() async {
    final result = await _zk._get('/state/root');
    return result['root'] as String;
  }

  /// Get account state
  Future<AccountState> getAccount(String address) async {
    final result = await _zk._get('/state/account/$address');
    return AccountState.fromJson(result);
  }

  /// Get merkle proof for account inclusion
  Future<MerkleProof> getMerkleProof(String address) async {
    final result = await _zk._get('/state/proof/$address');
    return MerkleProof.fromJson(result);
  }

  /// Verify merkle proof
  Future<bool> verifyMerkleProof(MerkleProof proof) async {
    final result = await _zk._post('/state/proof/verify', proof.toJson());
    return result['valid'] as bool;
  }

  /// Get state at specific batch
  Future<Map<String, dynamic>> getStateAtBatch(int batchNumber) async {
    return _zk._get('/state/batch/$batchNumber');
  }
}

/// Ceremony sub-client (trusted setup)
class CeremonyClient {
  final SynorZk _zk;

  CeremonyClient(this._zk);

  /// Get ceremony status
  Future<TrustedSetup> getStatus(
    CircuitType circuitType, [
    ProofSystem? system,
  ]) async {
    final sys = system ?? _zk.defaultProofSystem;
    final result = await _zk._get('/ceremony/${circuitType.value}?system=${sys.value}');
    return TrustedSetup.fromJson(result);
  }

  /// Contribute to ceremony
  Future<CeremonyContribution> contribute(
    CircuitType circuitType,
    Uint8List entropy, [
    ProofSystem? system,
  ]) async {
    final sys = system ?? _zk.defaultProofSystem;
    final result = await _zk._post('/ceremony/contribute', {
      'circuit_type': circuitType.value,
      'entropy': base64Encode(entropy),
      'system': sys.value,
    });
    return CeremonyContribution.fromJson(result);
  }

  /// Verify a contribution
  Future<bool> verifyContribution(
    CircuitType circuitType,
    String contributionHash,
  ) async {
    final result = await _zk._post('/ceremony/verify', {
      'circuit_type': circuitType.value,
      'contribution_hash': contributionHash,
    });
    return result['valid'] as bool;
  }

  /// List contributions
  Future<List<CeremonyContribution>> listContributions(
    CircuitType circuitType,
  ) async {
    final result = await _zk._get('/ceremony/${circuitType.value}/contributions');
    final contributions = result['contributions'] as List?;
    return contributions
            ?.map((c) => CeremonyContribution.fromJson(c as Map<String, dynamic>))
            .toList() ??
        [];
  }
}
