import 'dart:io';

import 'package:comunic/helpers/events_helper.dart';
import 'package:comunic/helpers/preferences_helper.dart';
import 'package:comunic/models/api_request.dart';
import 'package:comunic/models/api_response.dart';
import 'package:comunic/models/config.dart';
import 'package:dio/dio.dart';

/// API Helper
///
/// @author Pierre HUBERT

class APIHelper {
  /// Execute a [request] on the server and returns a [APIResponse]
  ///
  /// This method should never throw but the response code of the [APIResponse]
  /// should be verified before accessing response content
  Future<APIResponse> exec(APIRequest request, {bool multipart = false}) async {
    try {
      //Add API tokens
      request.addString("client", config().clientName);

      //Add user tokens (if required)
      if (request.needLogin) {
        final token = (await PreferencesHelper.getInstance()).getLoginToken();
        assert(token != null);
        request.addString("token", token);
      }

      // Determine server URL
      final path = config().apiServerUri + request.uri;
      Uri url;
      if (!config().apiServerSecure)
        url = Uri.http(config().apiServerName, path);
      else
        url = Uri.https(config().apiServerName, path);

      final data = FormData.fromMap(request.args);

      // Process files (if required)
      if (multipart) {
        // Process filesystem files
        for (final key in request.files.keys) {
          var v = request.files[key];
          data.files.add(MapEntry(
              key,
              await MultipartFile.fromFile(v.path,
                  filename: v.path.split("/").last)));
        }

        // Process in-memory files
        for (final key in request.bytesFiles.keys) {
          var v = request.bytesFiles[key];
          data.files.add(MapEntry(
              key,
              MultipartFile.fromBytes(
                v.bytes,
                filename: v.filename.split("/").last,
                contentType: v.type,
              )));
        }

        // Process picked files
        for (final key in request.pickedFiles.keys) {
          var v = request.pickedFiles[key];
          data.files.add(MapEntry(
              key,
              MultipartFile.fromBytes(
                await v.readAsBytes(),
                filename: v.path.split("/").last,
              )));
        }
      }

      // Execute the request
      final response = await Dio().post(
        url.toString(),
        data: data,
        options: Options(
          receiveDataWhenStatusError: true,
          validateStatus: (s) => true,
          responseType: ResponseType.plain,
        ),
      );

      // Check if login token is rejected by server
      if (response.statusCode == 412)
        EventsHelper.emit(InvalidLoginTokensEvent());

      if (response.statusCode != HttpStatus.ok)
        return APIResponse(response.statusCode, response.data);

      return APIResponse(response.statusCode, response.data);
    } catch (e, stack) {
      print(e.toString());
      print("Could not execute a request!");
      print(stack);
      return APIResponse(-1, null);
    }
  }

  /// Same as exec, but also allows to send files
  Future<APIResponse> execWithFiles(APIRequest request) async {
    return await exec(request, multipart: true);
  }
}