import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:http/io_client.dart';
import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/utilities/api_exception.dart';

typedef FactoryConstructor<U> = U Function(dynamic);

abstract class ApiClient {
  Future<U> postJsonForObject<T, U>(FactoryConstructor<U> factoryConstructor, String url, T jsonObject,
      {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0});

  Future<Response> postJsonForResponse<T>(String url, T jsonObject, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0});

  Future<U> getJsonForObject<T, U>(FactoryConstructor<U> factoryConstructor, String url, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0});

  Future<Response> getJsonForResponse<T>(String url, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0});
}

class ApiClientImp implements ApiClient {
  @override
  Future<U> postJsonForObject<T, U>(FactoryConstructor<U> factoryConstructor, String url, T jsonObject,
      {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0}) async {
    var headers0 = {'Accept': 'application/json'};
    if (headers != null && headers.isNotEmpty) {
      headers0.addAll(headers);
    }

    var response = await postJsonForResponse(url, jsonObject, token: token, queryParameters: queryParameters, headers: headers0, retryTimes: retryTimes);
    try {
      if (!kReleaseMode) {
        log("statusCode:${response.statusCode}");
      }
      var jsonData = jsonDecode(response.body);
      return factoryConstructor(jsonData);
    } catch (ex) {
      log(ex.toString());
      log("exception:$ex");
      throw APIException(APIException.badResponseFormat, arguments: ex);
    }
  }

  @override
  Future<Response> postJsonForResponse<T>(String url, T jsonObject, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0}) async {
    String? requestBody;
    if (jsonObject != null) {
      requestBody = jsonEncode(jsonObject);
      if (headers == null) {
        headers = {'Content-Type': 'application/json'};
      } else {
        headers['Content-Type'] = 'application/json';
      }
    }

    return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes);
  }

  Future<Response> _postForResponse(
    String url,
    requestBody, {
    String? token,
    Map<String, dynamic>? queryParameters,
    Map<String, String>? headers,
    int retryTimes = 0,
  }) async {
    try {
      var headers0 = <String, String>{};
      if (token != null) {
        headers0['Authorization'] = 'Bearer $token';
      }

      if (headers != null && headers.isNotEmpty) {
        headers0.addAll(headers);
      }

      if (queryParameters != null) {
        var queryString = Uri(queryParameters: queryParameters).query;
        url = '$url?$queryString';
      }
      Response response;

      response = await _post(Uri.parse(url), body: requestBody, headers: headers0).timeout(const Duration(seconds: 100));

      if (!kReleaseMode) {
        logger.d("------URL------");
        logger.i(url);
        logger.d("------Payload------");
        logger.i(jsonDecode(requestBody));
        logger.d("------Response------");
        logger.i(jsonDecode(response.body));
      }
      if (response.statusCode >= 200 && response.statusCode < 500) {
        var jsonData = jsonDecode(response.body);
        if (jsonData["StatusMessage"] != null && jsonData["StatusMessage"] == "Unauthorized user attempt to access API") {
          return await _postForResponse(url, requestBody, token: '', queryParameters: queryParameters, headers: headers, retryTimes: retryTimes);
        }
        return response;
      } else {
        throw APIError.throwAPIException(response);
      }
    } on SocketException catch (e) {
      if (retryTimes > 0) {
        log('will retry after 3 seconds...');
        await Future.delayed(const Duration(seconds: 3));
        return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1);
      } else {
        throw APIException(APIException.other, arguments: e);
      }
    } on HttpException catch (e) {
      if (retryTimes > 0) {
        log('will retry after 3 seconds...');
        await Future.delayed(const Duration(seconds: 3));
        return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1);
      } else {
        throw APIException(APIException.other, arguments: e);
      }
    } on TimeoutException catch (e) {
      throw APIException(APIException.timeout, arguments: e);
    } on ClientException catch (e) {
      if (retryTimes > 0) {
        log('will retry after 3 seconds...');
        await Future.delayed(const Duration(seconds: 3));
        return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1);
      } else {
        throw APIException(APIException.other, arguments: e);
      }
    } catch (ex) {
      log("exception1:$ex");
      throw APIException(APIException.badResponseFormat, arguments: ex);
    }
  }

  bool _certificateCheck(X509Certificate cert, String host, int port) => true;

  Future<T> _withClient<T>(Future<T> Function(Client) fn) async {
    var httpClient = HttpClient()..badCertificateCallback = _certificateCheck;
    var client = IOClient(httpClient);
    try {
      return await fn(client);
    } finally {
      client.close();
    }
  }

  Future<Response> _post(url, {Map<String, String>? headers, body, Encoding? encoding}) => _withClient((client) => client.post(url, headers: headers, body: body, encoding: encoding));

  @override
  Future<U> getJsonForObject<T, U>(FactoryConstructor<U> factoryConstructor, String url,
      {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0}) async {
    var headers0 = {'Accept': 'application/json'};
    if (headers != null && headers.isNotEmpty) {
      headers0.addAll(headers);
    }
    var response = await getJsonForResponse(url, token: token, queryParameters: queryParameters, headers: headers0, retryTimes: retryTimes);
    try {
      var jsonData = jsonDecode(response.body);
      return factoryConstructor(jsonData);
    } catch (ex) {
      log("exception:${response.body}");
      throw APIException(APIException.badResponseFormat, arguments: ex);
    }
  }

  @override
  Future<Response> getJsonForResponse<T>(String url, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0}) async {
    if (headers == null) {
      headers = {'Content-Type': 'application/json'};
    } else {
      headers['Content-Type'] = 'application/json';
    }
    return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes);
  }

  bool isFirstCall = true;

  Future<Response> _getForResponse(String url, {String? token, Map<String, dynamic>? queryParameters, Map<String, String>? headers, int retryTimes = 0}) async {
    try {
      var headers0 = <String, String>{};
      if (token != null) {
        headers0['Authorization'] = 'Bearer $token';
      }

      if (headers != null && headers.isNotEmpty) {
        headers0.addAll(headers);
      }
      if (queryParameters != null) {
        String queryString = Uri(queryParameters: queryParameters).query;
        if (isFirstCall) url = '$url?$queryString';
      }

      if (!kReleaseMode) {
        logger.i(url);
        logger.i("$queryParameters");
      }
      var response = await _get(Uri.parse(url), headers: headers0).timeout(const Duration(seconds: 60));

      if (!kReleaseMode) {
        logger.i(jsonDecode(response.body));
      }
      if (response.statusCode >= 200 && response.statusCode < 500) {
        var jsonData = jsonDecode(response.body);
        if (jsonData["StatusMessage"] != null && jsonData["StatusMessage"] == "Unauthorized user attempt to access API") {
          isFirstCall = false;
          return await _getForResponse(url, token: '', queryParameters: queryParameters, headers: headers, retryTimes: retryTimes);
        }
        return response;
      } else {
        throw APIError.throwAPIException(response);
      }
    } on SocketException catch (e) {
      if (retryTimes > 0) {
        log('will retry after 3 seconds...');
        await Future.delayed(const Duration(seconds: 3));
        return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1);
      } else {
        throw APIException(APIException.other, arguments: e);
      }
    } on HttpException catch (e) {
      if (retryTimes > 0) {
        log('will retry after 3 seconds...');
        await Future.delayed(const Duration(seconds: 3));
        return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1);
      } else {
        throw APIException(APIException.other, arguments: e);
      }
    } on TimeoutException catch (e) {
      throw APIException(APIException.timeout, arguments: e);
    } on ClientException catch (e) {
      if (retryTimes > 0) {
        await Future.delayed(const Duration(seconds: 3));
        return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1);
      } else {
        throw APIException(APIException.other, arguments: e);
      }
    }
  }

  Future<Response> _get(url, {Map<String, String>? headers}) => _withClient((client) => client.get(url, headers: headers));
}
