import 'package:http/http.dart';
import 'package:mime/mime.dart' show lookupMimeType;
import 'package:minio/src/minio_errors.dart';
import 'package:minio/src/minio_models_generated.dart';
import 'package:xml/xml.dart' as xml;

bool isValidBucketName(String bucket) {
  if (bucket == null) return false;

  if (bucket.length < 3 || bucket.length > 63) {
    return false;
  }
  if (bucket.contains('..')) {
    return false;
  }

  if (RegExp(r'[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+').hasMatch(bucket)) {
    return false;
  }

  if (RegExp(r'^[a-z0-9][a-z0-9.-]+[a-z0-9]$').hasMatch(bucket)) {
    return true;
  }

  return false;
}

bool isValidObjectName(objectName) {
  if (!isValidPrefix(objectName)) return false;
  if (objectName.isEmpty) return false;
  return true;
}

bool isValidPrefix(String prefix) {
  if (prefix == null) return false;
  if (prefix.length > 1024) return false;
  return true;
}

bool isAmazonEndpoint(String endpoint) {
  return endpoint == 's3.amazonaws.com' ||
      endpoint == 's3.cn-north-1.amazonaws.com.cn';
}

bool isVirtualHostStyle(String endpoint, bool useSSL, String bucket) {
  if (bucket == null) {
    return false;
  }

  if (useSSL && bucket.contains('.')) {
    return false;
  }

  return isAmazonEndpoint(endpoint);
}

bool isValidEndpoint(endpoint) {
  return isValidDomain(endpoint) || isValidIPv4(endpoint);
}

bool isValidIPv4(String ip) {
  if (ip == null) return false;
  return RegExp(r'^(\d{1,3}\.){3,3}\d{1,3}$').hasMatch(ip);
}

bool isValidDomain(String host) {
  if (host == null) return false;

  if (host.isEmpty || host.length > 255) {
    return false;
  }

  if (host.startsWith('-') || host.endsWith('-')) {
    return false;
  }

  if (host.startsWith('_') || host.endsWith('_')) {
    return false;
  }

  if (host.startsWith('.') || host.endsWith('.')) {
    return false;
  }

  final alphaNumerics = '`~!@#\$%^&*()+={}[]|\\"\';:><?/'.split('');
  for (var char in alphaNumerics) {
    if (host.contains(char)) return false;
  }

  return true;
}

bool isValidPort(int port) {
  if (port == null) return false;
  if (port < 0) return false;
  if (port == 0) return true;
  const minPort = 1;
  const maxPort = 65535;
  return port >= minPort && port <= maxPort;
}

int implyPort(bool ssl) {
  return ssl ? 443 : 80;
}

String makeDateLong(DateTime date) {
  final isoDate = date.toIso8601String();

  // 'YYYYMMDDTHHmmss' + Z
  return isoDate.substring(0, 4) +
      isoDate.substring(5, 7) +
      isoDate.substring(8, 13) +
      isoDate.substring(14, 16) +
      isoDate.substring(17, 19) +
      'Z';
}

String makeDateShort(DateTime date) {
  final isoDate = date.toIso8601String();

  // 'YYYYMMDD'
  return isoDate.substring(0, 4) +
      isoDate.substring(5, 7) +
      isoDate.substring(8, 10);
}

Map<String, String> prependXAMZMeta(Map<String, String> metadata) {
  final newMetadata = Map<String, String>.from(metadata);
  for (var key in metadata.keys) {
    if (!isAmzHeader(key) &&
        !isSupportedHeader(key) &&
        !isStorageclassHeader(key)) {
      newMetadata['x-amz-meta-' + key] = newMetadata[key];
      newMetadata.remove(key);
    }
  }
  return newMetadata;
}

bool isAmzHeader(key) {
  key = key.toLowerCase();
  return key.startsWith('x-amz-meta-') ||
      key == 'x-amz-acl' ||
      key.startsWith('x-amz-server-side-encryption-') ||
      key == 'x-amz-server-side-encryption';
}

bool isSupportedHeader(key) {
  var supported_headers = {
    'content-type',
    'cache-control',
    'content-encoding',
    'content-disposition',
    'content-language',
    'x-amz-website-redirect-location',
  };
  return (supported_headers.contains(key.toLowerCase()));
}

bool isStorageclassHeader(key) {
  return key.toLowerCase() == 'x-amz-storage-class';
}

Map<String, String> extractMetadata(Map<String, String> metaData) {
  var newMetadata = <String, String>{};
  for (var key in metaData.keys) {
    if (isSupportedHeader(key) ||
        isStorageclassHeader(key) ||
        isAmzHeader(key)) {
      if (key.toLowerCase().startsWith('x-amz-meta-')) {
        newMetadata[key.substring(11, key.length)] = metaData[key];
      } else {
        newMetadata[key] = metaData[key];
      }
    }
  }
  return newMetadata;
}

String probeContentType(String path) {
  final contentType = lookupMimeType(path);
  return contentType ?? 'application/octet-stream';
}

Map<String, String> insertContentType(
  Map<String, String> metaData,
  String filePath,
) {
  for (var key in metaData.keys) {
    if (key.toLowerCase() == 'content-type') {
      return metaData;
    }
  }

  final newMetadata = Map<String, String>.from(metaData);
  newMetadata['content-type'] = probeContentType(filePath);
  return newMetadata;
}

Future<void> validateStreamed(
  StreamedResponse streamedResponse, {
  int expect,
}) async {
  if (streamedResponse.statusCode >= 400) {
    final response = await Response.fromStream(streamedResponse);
    final body = xml.XmlDocument.parse(response.body);
    final error = Error.fromXml(body.rootElement);
    throw MinioS3Error(error.message, error, response);
  }

  if (expect != null && streamedResponse.statusCode != expect) {
    final response = await Response.fromStream(streamedResponse);
    throw MinioS3Error(
        '$expect expected, got ${streamedResponse.statusCode}', null, response);
  }
}

void validate(Response response, {int expect}) {
  if (response.statusCode >= 400) {
    var error;

    // Parse HTTP response body as XML only when not empty
    if (response.body == null || response.body.isEmpty) {
      error = Error(response.reasonPhrase, null, response.reasonPhrase, null);
    } else {
      final body = xml.XmlDocument.parse(response.body);
      error = Error.fromXml(body.rootElement);
    }

    throw MinioS3Error(error?.message, error, response);
  }

  if (expect != null && response.statusCode != expect) {
    throw MinioS3Error(
        '$expect expected, got ${response.statusCode}', null, response);
  }
}