import 'dart:typed_data';

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

import '../invalid_key_exception.dart';
import '../util/key_helper.dart';
import 'djb_ec_private_key.dart';
import 'djb_ec_public_key.dart';
import 'ec_key_pair.dart';
import 'ec_private_key.dart';
import 'ec_public_key.dart';
import 'ed25519.dart';

typedef KeyPairGenerator = GeneratedKeyPair Function();
typedef AgreementCalculator = Uint8List Function(Uint8List, Uint8List);

class GeneratedKeyPair {
  GeneratedKeyPair(this.private, this.public);

  final Uint8List private;
  final Uint8List public;
}

class Curve {
  static const int djbType = 0x05;

  static KeyPairGenerator? keyPairGenerator;
  static AgreementCalculator? agreementCalculator;

  static ECKeyPair generateKeyPair() {
    final x25519.KeyPair keyPair;
    final generator = keyPairGenerator;
    if (generator != null) {
      final kp = generator();
      keyPair = x25519.KeyPair(privateKey: kp.private, publicKey: kp.public);
    } else {
      keyPair = x25519.generateKeyPair();
    }

    return ECKeyPair(DjbECPublicKey(Uint8List.fromList(keyPair.publicKey)),
        DjbECPrivateKey(Uint8List.fromList(keyPair.privateKey)));
  }

  static ECKeyPair generateKeyPairFromPrivate(List<int> private) {
    if (private.length != 32) {
      throw InvalidKeyException(
          'Invalid private key length: ${private.length}');
    }
    final public = List<int>.filled(32, 0);

    private[0] &= 248;
    private[31] &= 127;
    private[31] |= 64;

    x25519.ScalarBaseMult(public, private);

    return ECKeyPair(DjbECPublicKey(Uint8List.fromList(public)),
        DjbECPrivateKey(Uint8List.fromList(private)));
  }

  static ECPublicKey decodePointList(List<int> bytes, int offset) =>
      decodePoint(Uint8List.fromList(bytes), offset);

  static ECPublicKey decodePoint(Uint8List bytes, int offset) {
    if (bytes.length - offset < 1) {
      throw InvalidKeyException('No key type identifier');
    }

    final type = bytes[offset] & 0xFF;

    switch (type) {
      case Curve.djbType:
        if (bytes.length - offset < 33) {
          throw InvalidKeyException('Bad key length: ${bytes.length}');
        }

        final keyBytes = Uint8List(32);
        arraycopy(bytes, offset + 1, keyBytes, 0, keyBytes.length);
        return DjbECPublicKey(keyBytes);
      default:
        throw InvalidKeyException('Bad key type: $type');
    }
  }

  static void arraycopy(
      List<int> src, int srcPos, List<int> dest, int destPos, int length) {
    dest.setRange(destPos, length + destPos, src, srcPos);
  }

  static ECPrivateKey decodePrivatePoint(Uint8List bytes) =>
      DjbECPrivateKey(bytes);

  static Uint8List calculateAgreement(
      ECPublicKey? publicKey, ECPrivateKey? privateKey) {
    if (publicKey == null) {
      throw Exception('publicKey value is null');
    }

    if (privateKey == null) {
      throw Exception('privateKey value is null');
    }
    if (publicKey.getType() != privateKey.getType()) {
      throw Exception('Public and private keys must be of the same type!');
    }

    if (publicKey.getType() == djbType) {
      final calculator = agreementCalculator;
      if (calculator != null) {
        return calculator(
          (privateKey as DjbECPrivateKey).privateKey,
          (publicKey as DjbECPublicKey).publicKey,
        );
      }

      final secretKey = x25519.X25519(
        List<int>.from((privateKey as DjbECPrivateKey).privateKey),
        List<int>.from((publicKey as DjbECPublicKey).publicKey),
      );
      return secretKey;
    } else {
      throw Exception('Unknown type: ${publicKey.getType()}');
    }
  }

  static bool verifySignature(
      ECPublicKey? signingKey, Uint8List? message, Uint8List? signature) {
    if (signingKey == null || message == null || signature == null) {
      throw InvalidKeyException('Values must not be null');
    }

    if (signingKey.getType() == djbType) {
      if (signature.length != 64) {
        return false;
      }

      final publicKey = (signingKey as DjbECPublicKey).publicKey;
      return verifySig(publicKey, message, signature);
    } else {
      throw InvalidKeyException(
          'Unknown Signing Key type${signingKey.getType()}');
    }
  }

  static Uint8List calculateSignature(
      ECPrivateKey? signingKey, Uint8List? message) {
    if (signingKey == null || message == null) {
      throw Exception('Values must not be null');
    }

    if (signingKey.getType() == djbType) {
      final privateKey = signingKey.serialize();
      final random = generateRandomBytes();

      return sign(privateKey, message, random);
    } else {
      throw Exception('Unknown Signing Key type${signingKey.getType()}');
    }
  }

  static Uint8List calculateVrfSignature(
      ECPrivateKey? signingKey, Uint8List? message) {
    if (signingKey == null || message == null) {
      throw Exception('Values must not be null');
    }

    if (signingKey.getType() == djbType) {
      // TODO
    } else {
      throw Exception('Unknown Signing Key type${signingKey.getType()}');
    }
    return Uint8List(0);
  }

  static Uint8List verifyVrfSignature(
      ECPublicKey? signingKey, Uint8List? message, Uint8List? signature) {
    if (signingKey == null || message == null || signature == null) {
      throw Exception('Values must not be null');
    }

    if (signingKey.getType() == djbType) {
      // TODO
    } else {
      throw Exception('Unknown Signing Key type${signingKey.getType()}');
    }
    return Uint8List(0);
  }
}
