/// Synor Storage SDK client for Flutter/Dart.
library synor_storage;

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

export 'types.dart';

/// Synor Storage SDK client for Flutter/Dart.
///
/// Provides IPFS-compatible decentralized storage with upload, download,
/// pinning, and CAR file operations.
///
/// Example:
/// ```dart
/// final storage = SynorStorage(StorageConfig(apiKey: 'your-api-key'));
///
/// // Upload a file
/// final data = utf8.encode('Hello, World!');
/// final result = await storage.upload(
///   Uint8List.fromList(data),
///   options: UploadOptions(name: 'hello.txt'),
/// );
/// print('CID: ${result.cid}');
///
/// // Download content
/// final content = await storage.download(result.cid);
/// print('Content: ${utf8.decode(content)}');
///
/// // Pin content
/// final pin = await storage.pin(PinRequest(cid: result.cid, durationDays: 30));
/// print('Pin status: ${pin.status}');
///
/// storage.close();
/// ```
class SynorStorage {
  final StorageConfig config;
  final http.Client _client;

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

  // Upload operations

  /// Upload data to storage.
  Future<UploadResponse> upload(
    Uint8List data, {
    UploadOptions options = const UploadOptions(),
  }) async {
    return _executeWithRetry(() async {
      final request = http.MultipartRequest(
        'POST',
        Uri.parse('${config.endpoint}/upload'),
      );

      request.headers['Authorization'] = 'Bearer ${config.apiKey}';

      request.files.add(http.MultipartFile.fromBytes(
        'file',
        data,
        filename: options.name ?? 'file',
      ));

      if (options.name != null) {
        request.fields['name'] = options.name!;
      }
      request.fields['wrap_with_directory'] = options.wrapWithDirectory.toString();
      request.fields['hash_algorithm'] = options.hashAlgorithm.toJson();
      if (options.chunkSize != null) {
        request.fields['chunk_size'] = options.chunkSize.toString();
      }
      if (options.pinDurationDays != null) {
        request.fields['pin_duration_days'] = options.pinDurationDays.toString();
      }

      final streamedResponse = await request.send().timeout(config.timeout);
      final response = await http.Response.fromStream(streamedResponse);

      if (response.statusCode >= 200 && response.statusCode < 300) {
        final json = jsonDecode(response.body) as Map<String, dynamic>;
        return UploadResponse.fromJson(json);
      }

      throw StorageException(
        'Upload failed: ${response.body}',
        statusCode: response.statusCode,
      );
    });
  }

  /// Upload multiple files as a directory.
  Future<UploadResponse> uploadDirectory(
    List<FileEntry> files, {
    String? dirName,
  }) async {
    return _executeWithRetry(() async {
      final request = http.MultipartRequest(
        'POST',
        Uri.parse('${config.endpoint}/upload/directory'),
      );

      request.headers['Authorization'] = 'Bearer ${config.apiKey}';

      for (final file in files) {
        request.files.add(http.MultipartFile.fromBytes(
          'files',
          file.content,
          filename: file.path,
        ));
      }

      if (dirName != null) {
        request.fields['name'] = dirName;
      }

      final streamedResponse = await request.send().timeout(config.timeout);
      final response = await http.Response.fromStream(streamedResponse);

      if (response.statusCode >= 200 && response.statusCode < 300) {
        final json = jsonDecode(response.body) as Map<String, dynamic>;
        return UploadResponse.fromJson(json);
      }

      throw StorageException(
        'Directory upload failed: ${response.body}',
        statusCode: response.statusCode,
      );
    });
  }

  // Download operations

  /// Download content by CID.
  Future<Uint8List> download(String cid) async {
    return _executeWithRetry(() async {
      final response = await _client
          .get(
            Uri.parse('${config.endpoint}/content/$cid'),
            headers: {'Authorization': 'Bearer ${config.apiKey}'},
          )
          .timeout(config.timeout);

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

      throw StorageException(
        'Download failed: ${response.body}',
        statusCode: response.statusCode,
      );
    });
  }

  /// Download content as a stream.
  Stream<List<int>> downloadStream(String cid) async* {
    final request = http.Request(
      'GET',
      Uri.parse('${config.endpoint}/content/$cid'),
    );
    request.headers['Authorization'] = 'Bearer ${config.apiKey}';

    final response = await _client.send(request);

    if (response.statusCode < 200 || response.statusCode >= 300) {
      throw StorageException(
        'Download failed',
        statusCode: response.statusCode,
      );
    }

    yield* response.stream;
  }

  /// Get the gateway URL for content.
  String getGatewayUrl(String cid, {String? path}) {
    if (path != null) {
      return '${config.gateway}/ipfs/$cid/$path';
    }
    return '${config.gateway}/ipfs/$cid';
  }

  // Pinning operations

  /// Pin content to ensure availability.
  Future<Pin> pin(PinRequest request) async {
    return _post('/pins', request.toJson(), Pin.fromJson);
  }

  /// Unpin content.
  Future<void> unpin(String cid) async {
    await _delete('/pins/$cid');
  }

  /// Get pin status.
  Future<Pin> getPinStatus(String cid) async {
    return _get('/pins/$cid', Pin.fromJson);
  }

  /// List all pins.
  Future<List<Pin>> listPins({
    PinStatus? status,
    int limit = 50,
    int offset = 0,
  }) async {
    var path = '/pins?limit=$limit&offset=$offset';
    if (status != null) {
      path += '&status=${status.toJson()}';
    }
    return _getList(path, Pin.fromJson);
  }

  // CAR file operations

  /// Create a CAR file from entries.
  Future<CarFile> createCar(List<CarEntry> entries) async {
    return _executeWithRetry(() async {
      final request = http.MultipartRequest(
        'POST',
        Uri.parse('${config.endpoint}/car'),
      );

      request.headers['Authorization'] = 'Bearer ${config.apiKey}';

      for (final entry in entries) {
        request.files.add(http.MultipartFile.fromBytes(
          'files',
          entry.content,
          filename: entry.path,
        ));
      }

      final streamedResponse = await request.send().timeout(config.timeout);
      final response = await http.Response.fromStream(streamedResponse);

      if (response.statusCode >= 200 && response.statusCode < 300) {
        final json = jsonDecode(response.body) as Map<String, dynamic>;
        return CarFile.fromJson(json);
      }

      throw StorageException(
        'CAR creation failed: ${response.body}',
        statusCode: response.statusCode,
      );
    });
  }

  /// Import a CAR file.
  Future<List<String>> importCar(Uint8List carData) async {
    return _executeWithRetry(() async {
      final request = http.MultipartRequest(
        'POST',
        Uri.parse('${config.endpoint}/car/import'),
      );

      request.headers['Authorization'] = 'Bearer ${config.apiKey}';

      request.files.add(http.MultipartFile.fromBytes(
        'file',
        carData,
        filename: 'archive.car',
      ));

      final streamedResponse = await request.send().timeout(config.timeout);
      final response = await http.Response.fromStream(streamedResponse);

      if (response.statusCode >= 200 && response.statusCode < 300) {
        final json = jsonDecode(response.body) as Map<String, dynamic>;
        return (json['cids'] as List).cast<String>();
      }

      throw StorageException(
        'CAR import failed: ${response.body}',
        statusCode: response.statusCode,
      );
    });
  }

  /// Export content as a CAR file.
  Future<Uint8List> exportCar(String cid) async {
    return _executeWithRetry(() async {
      final response = await _client
          .get(
            Uri.parse('${config.endpoint}/car/$cid'),
            headers: {'Authorization': 'Bearer ${config.apiKey}'},
          )
          .timeout(config.timeout);

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

      throw StorageException(
        'CAR export failed: ${response.body}',
        statusCode: response.statusCode,
      );
    });
  }

  // Directory operations

  /// List directory contents.
  Future<List<DirectoryEntry>> listDirectory(String cid) async {
    return _getList('/content/$cid/ls', DirectoryEntry.fromJson);
  }

  // Statistics

  /// Get storage statistics.
  Future<StorageStats> getStats() async {
    return _get('/stats', StorageStats.fromJson);
  }

  // HTTP helper methods

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

      return _handleResponse(response, fromJson);
    });
  }

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

      if (response.statusCode >= 200 && response.statusCode < 300) {
        final List<dynamic> jsonList = jsonDecode(response.body);
        return jsonList
            .map((e) => fromJson(e as Map<String, dynamic>))
            .toList();
      }

      throw StorageException(
        'HTTP error: ${response.body}',
        statusCode: response.statusCode,
      );
    });
  }

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

      return _handleResponse(response, fromJson);
    });
  }

  Future<void> _delete(String path) async {
    return _executeWithRetry(() async {
      final response = await _client
          .delete(
            Uri.parse('${config.endpoint}$path'),
            headers: _headers,
          )
          .timeout(config.timeout);

      if (response.statusCode < 200 || response.statusCode >= 300) {
        throw StorageException(
          'Delete failed: ${response.body}',
          statusCode: response.statusCode,
        );
      }
    });
  }

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

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

    throw StorageException(
      'HTTP error: ${response.body}',
      statusCode: response.statusCode,
    );
  }

  Future<T> _executeWithRetry<T>(Future<T> Function() operation) async {
    Exception? lastError;

    for (var attempt = 0; attempt < config.retries; attempt++) {
      try {
        return await operation();
      } catch (e) {
        lastError = e as Exception;
        if (config.debug) {
          print('Attempt ${attempt + 1} failed: $e');
        }
        if (attempt < config.retries - 1) {
          await Future.delayed(Duration(seconds: attempt + 1));
        }
      }
    }

    throw lastError ?? StorageException('Unknown error after ${config.retries} retries');
  }

  /// Close the client and release resources.
  void close() {
    _client.close();
  }
}
