import 'dart:convert';

import 'package:crypto/crypto.dart';
import 'package:intl/intl.dart';

class AwsSignature {
  final String accessKey;
  final String secretKey;
  final String region;
  final String serviceName;

  AwsSignature({
    required this.accessKey,
    required this.secretKey,
    required this.region,
    required this.serviceName,
  });

  Map<String, String> buildRequestHeaders({
    required String method,
    required Uri uri,
    required Map<String, dynamic> payload,
    required DateTime dateTime,
  }) {
    // Format date strings for AWS signature
    final amzDate = _formatAmzDate(dateTime);
    final dateStamp = _formatDateStamp(dateTime);

    // Create canonical request components
    final canonicalUri = uri.path.isEmpty ? '/' : uri.path;
    final canonicalQueryString = _buildCanonicalQueryString(uri);
    final payloadJson = jsonEncode(payload);
    final payloadHash = _hashString(payloadJson);

    // Default headers for AWS Recognition
    final headers = {
      'content-type': 'application/x-amz-json-1.1',
      'host': uri.host,
      'x-amz-date': amzDate,
      'x-amz-target': 'RekognitionService.$method',
    };

    // Create canonical headers string
    final canonicalHeaders = _buildCanonicalHeaders(headers);
    final signedHeaders = _buildSignedHeaders(headers);

    // Build canonical request
    final canonicalRequest = [
      method,
      canonicalUri,
      canonicalQueryString,
      canonicalHeaders,
      signedHeaders,
      payloadHash,
    ].join('\n');

    // Create string to sign
    final algorithm = 'AWS4-HMAC-SHA256';
    final credentialScope = '$dateStamp/$region/$serviceName/aws4_request';
    final stringToSign = [
      algorithm,
      amzDate,
      credentialScope,
      _hashString(canonicalRequest),
    ].join('\n');

    // Calculate signature
    final signature = _calculateSignature(
      secretKey: secretKey,
      dateStamp: dateStamp,
      region: region,
      serviceName: serviceName,
      stringToSign: stringToSign,
    );

    // Create Authorization header
    final authorizationHeader = [
      '$algorithm Credential=$accessKey/$credentialScope',
      'SignedHeaders=$signedHeaders',
      'Signature=$signature',
    ].join(', ');

    // Return final headers
    return {...headers, 'Authorization': authorizationHeader};
  }

  String _formatAmzDate(DateTime dateTime) {
    return DateFormat("yyyyMMdd'T'HHmmss'Z'").format(dateTime);
  }

  String _formatDateStamp(DateTime dateTime) {
    return DateFormat('yyyyMMdd').format(dateTime);
  }

  String _buildCanonicalQueryString(Uri uri) {
    if (uri.queryParameters.isEmpty) {
      return '';
    }

    final sortedParameters = uri.queryParameters.keys.toList()..sort();
    return sortedParameters
        .map((key) => '$key=${Uri.encodeComponent(uri.queryParameters[key]!)}')
        .join('&');
  }

  String _buildCanonicalHeaders(Map<String, String> headers) {
    final sortedKeys =
        headers.keys.map((k) => k.toLowerCase()).toList()..sort();
    return sortedKeys.map((key) => '$key:${headers[key]?.trim()}\n').join();
  }

  String _buildSignedHeaders(Map<String, String> headers) {
    final sortedKeys =
        headers.keys.map((k) => k.toLowerCase()).toList()..sort();
    return sortedKeys.join(';');
  }

  String _hashString(String input) {
    final bytes = utf8.encode(input);
    final digest = sha256.convert(bytes);
    return digest.toString();
  }

  String _calculateSignature({
    required String secretKey,
    required String dateStamp,
    required String region,
    required String serviceName,
    required String stringToSign,
  }) {
    // Create signing key
    final kDate = _hmacSha256('AWS4$secretKey', dateStamp);
    final kRegion = _hmacSha256(kDate, region);
    final kService = _hmacSha256(kRegion, serviceName);
    final kSigning = _hmacSha256(kService, 'aws4_request');

    // Calculate signature
    return _hmacSha256Hex(kSigning, stringToSign);
  }

  List<int> _hmacSha256(dynamic key, String message) {
    var hmac = Hmac(sha256, key is String ? utf8.encode(key) : key);
    var digest = hmac.convert(utf8.encode(message));
    return digest.bytes;
  }

  String _hmacSha256Hex(List<int> key, String message) {
    var hmac = Hmac(sha256, key);
    var digest = hmac.convert(utf8.encode(message));
    return digest.toString();
  }
}
