package encryption

import (
	"crypto/ecdh"
	"crypto/rand"
	"encoding/base64"
	"fmt"

	"github.com/metacubex/blake3"
	"github.com/metacubex/mlkem"
)

const MLKEM768SeedLength = mlkem.SeedSize
const MLKEM768ClientLength = mlkem.EncapsulationKeySize768
const X25519PasswordSize = 32
const X25519PrivateKeySize = 32

func GenMLKEM768(seedStr string) (seedBase64, clientBase64, hash32Base64 string, err error) {
	var seed [MLKEM768SeedLength]byte
	if len(seedStr) > 0 {
		s, _ := base64.RawURLEncoding.DecodeString(seedStr)
		if len(s) != MLKEM768SeedLength {
			err = fmt.Errorf("invalid length of ML-KEM-768 seed: %s", seedStr)
			return
		}
		seed = [MLKEM768SeedLength]byte(s)
	} else {
		_, err = rand.Read(seed[:])
		if err != nil {
			return
		}
	}

	key, _ := mlkem.NewDecapsulationKey768(seed[:])
	client := key.EncapsulationKey().Bytes()
	hash32 := blake3.Sum256(client)
	seedBase64 = base64.RawURLEncoding.EncodeToString(seed[:])
	clientBase64 = base64.RawURLEncoding.EncodeToString(client)
	hash32Base64 = base64.RawURLEncoding.EncodeToString(hash32[:])
	return
}

func GenX25519(privateKeyStr string) (privateKeyBase64, passwordBase64, hash32Base64 string, err error) {
	var privateKey [X25519PrivateKeySize]byte
	if len(privateKeyStr) > 0 {
		s, _ := base64.RawURLEncoding.DecodeString(privateKeyStr)
		if len(s) != X25519PrivateKeySize {
			err = fmt.Errorf("invalid length of X25519 private key: %s", privateKeyStr)
			return
		}
		privateKey = [X25519PrivateKeySize]byte(s)
	} else {
		_, err = rand.Read(privateKey[:])
		if err != nil {
			return
		}
	}

	// Avoid generating equivalent X25519 private keys
	// https://github.com/XTLS/Xray-core/pull/1747
	//
	// Modify random bytes using algorithm described at:
	// https://cr.yp.to/ecdh.html.
	privateKey[0] &= 248
	privateKey[31] &= 127
	privateKey[31] |= 64

	key, err := ecdh.X25519().NewPrivateKey(privateKey[:])
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	password := key.PublicKey().Bytes()
	hash32 := blake3.Sum256(password)
	privateKeyBase64 = base64.RawURLEncoding.EncodeToString(privateKey[:])
	passwordBase64 = base64.RawURLEncoding.EncodeToString(password)
	hash32Base64 = base64.RawURLEncoding.EncodeToString(hash32[:])
	return
}
