import 'dart:convert';

import 'package:http/http.dart' as http;

import 'package:http_parser/http_parser.dart';

import 'package:pshared/api/responses/file_uploaded.dart';
import 'package:pshared/api/responses/message.dart';
import 'package:pshared/api/responses/error/connectivity.dart';
import 'package:pshared/api/responses/error/server.dart';
import 'package:pshared/config/constants.dart';


Uri _uri(String service, String url) {
  // Ensure the base URL ends with a slash
  final normalizedBaseUrl = Constants.apiUrl.endsWith('/')
    ? Constants.apiUrl
    : '${Constants.apiUrl}/';

  // Remove leading slash from service, if any
  final normalizedService = service.startsWith('/') ? service.substring(1) : service;

  // Remove leading slash from url, if any
  final normalizedUrl = url.startsWith('/') ? url.substring(1) : url;

  // Only append a trailing slash to the service
  // if the URL is non-empty
  final serviceWithOptionalSlash = normalizedUrl.isNotEmpty ? '$normalizedService/' : normalizedService;

  return Uri.parse(normalizedBaseUrl).resolve(serviceWithOptionalSlash).resolve(normalizedUrl);
}



Map<String, String> _authHeader(Map<String, String> headers, String? authToken) {
  if (authToken != null && authToken.isNotEmpty) {
    headers['Authorization'] = 'Bearer $authToken';
  }
  return headers;
}

Map<String, String> _headers({String? authToken}) {
  final headers = {'Content-Type': 'application/json'};
  return _authHeader(headers, authToken);
}

Future<http.Response> postRequest(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
  final response = await http.post(_uri(service, url),
    headers: _headers(authToken: authToken),
    body: json.encode(body),
  );
  return response;
}

Future<http.Response> getRequest(String service, String url, {String? authToken}) async {
  final response = await http.get(_uri(service, url),
    headers: _headers(authToken: authToken),
  );
  return response;
}

Future<http.Response> putRequest(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
  final response = await http.put(_uri(service, url),
    headers: _headers(authToken: authToken),
    body: json.encode(body),
  );
  return response;
}

Future<http.Response> patchRequest(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
  final response = await http.patch(_uri(service, url),
    headers: _headers(authToken: authToken),
    body: json.encode(body),
  );
  return response;
}

Future<http.Response> deleteRequest(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
  final response = await http.delete(_uri(service, url),
    headers: _headers(authToken: authToken),
    body: json.encode(body),
  );
  return response;
}

Future<http.StreamedResponse> _fileUploadRequest(String service, String url, String fileName, String fileType, String mediaType, List<int> bytes, {String? authToken}) async {
  var request = http.MultipartRequest('POST', _uri(service, url));

  var multipartFile = http.MultipartFile.fromBytes(
    fileType,
    bytes,
    contentType: MediaType.parse(mediaType),
    filename: fileName,
  );

  request.files.add(multipartFile);

  if (authToken != null && authToken.isNotEmpty) {
    request.headers['Authorization'] = 'Bearer $authToken';
  }

  return request.send();
}

void _throwConnectivityError(http.Response response, Object e) {
  throw ConnectivityError(
    code: response.statusCode,
    message: e is FormatException 
      ? 'Invalid response format. error: ${e.toString()}' 
      : 'Unknown error occurred, error: ${e.toString()}',
  );
}

Future<Map<String, dynamic>> _handleResponse(Future<http.Response> r) async {
  late http.Response response;
  try {
    response = await r;
  } catch(e) {
    throw ConnectivityError(message: e.toString());
  }

  late HTTPMessage message;
  try {
    message = HTTPMessage.fromJson(json.decode(response.body));
  } catch(e) {
    _throwConnectivityError(response, e);
  }

  if (response.statusCode < 200 || response.statusCode >= 300) {
    late ErrorResponse error;
    try {
      error = ErrorResponse.fromJson(message.data);
    } catch(e) {
      _throwConnectivityError(response, e);
    }
    throw error;
  }

  return message.data;
}

Future<Map<String, dynamic>> getPOSTResponse(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
  return _handleResponse(postRequest(service, url, body, authToken: authToken));
}

Future<Map<String, dynamic>> getGETResponse(String service, String url, {String? authToken}) async {
  return _handleResponse(getRequest(service, url, authToken: authToken));
}

Future<Map<String, dynamic>> getPUTResponse(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
  return _handleResponse(putRequest(service, url, body, authToken: authToken));
}

Future<Map<String, dynamic>> getPATCHResponse(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
  return _handleResponse(patchRequest(service, url, body, authToken: authToken));
}

Future<Map<String, dynamic>> getDELETEResponse(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
return _handleResponse(deleteRequest(service, url, body, authToken: authToken));
}

Future<FileUploaded?> getFileUploadResponse(String service, String url, String fileName, String fileType, String mediaType, List<int> bytes, {String? authToken}) async {
  final streamedResponse = await _fileUploadRequest(service, url, fileName, fileType, mediaType, bytes, authToken: authToken);
  return FileUploaded.fromJson(await _handleResponse(http.Response.fromStream(streamedResponse)));
}

class BinaryResponse {
  final List<int> bytes;
  final Map<String, String> headers;
  final int statusCode;

  const BinaryResponse({
    required this.bytes,
    required this.headers,
    required this.statusCode,
  });

  String? header(String key) => headers[key.toLowerCase()];
}

Future<BinaryResponse> getBinaryGETResponse(String service, String url, {String? authToken}) async {
  late http.Response response;
  try {
    response = await getRequest(service, url, authToken: authToken);
  } catch (e) {
    throw ConnectivityError(message: e.toString());
  }

  if (response.statusCode < 200 || response.statusCode >= 300) {
    late HTTPMessage message;
    try {
      message = HTTPMessage.fromJson(json.decode(response.body));
    } catch (e) {
      _throwConnectivityError(response, e);
    }

    late ErrorResponse error;
    try {
      error = ErrorResponse.fromJson(message.data);
    } catch (e) {
      _throwConnectivityError(response, e);
    }
    throw error;
  }

  return BinaryResponse(
    bytes: response.bodyBytes,
    headers: response.headers,
    statusCode: response.statusCode,
  );
}
