// Package hybrid defines several hybrid classical/quantum KEMs.
//
// KEMs are combined by simple concatenation of shared secrets, cipher texts,
// public keys, etc, see
//
//   https://datatracker.ietf.org/doc/draft-ietf-tls-hybrid-design/
//   https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr2.pdf
//
// Note that this is only fine if the shared secret is used in its entirety
// in a next step, such as being hashed or used as key.
//
// For deriving a KEM keypair deterministically and encapsulating
// deterministically, we expand a single seed to both using SHAKE256,
// so that a non-uniform seed (such as a shared secret generated by a hybrid
// KEM where one of the KEMs is weak) doesn't impact just one of the KEMs.
package hybrid

import (
	"errors"

	"github.com/cloudflare/circl/hpke"
	"github.com/cloudflare/circl/internal/sha3"
	"github.com/cloudflare/circl/kem"
	"github.com/cloudflare/circl/kem/kyber/kyber1024"
	"github.com/cloudflare/circl/kem/kyber/kyber512"
	"github.com/cloudflare/circl/kem/kyber/kyber768"
)

var ErrUninitialized = errors.New("public or private key not initialized")

// Returns the hybrid KEM of Kyber512 and X25519.
func Kyber512X25519() kem.Scheme { return kyber512X }

// Returns the hybrid KEM of Kyber768 and X448.
func Kyber768X448() kem.Scheme { return kyber768X }

// Returns the hybrid KEM of Kyber1024 and X448.
func Kyber1024X448() kem.Scheme { return kyber1024X }

var kyber512X kem.Scheme = &scheme{
	"Kyber512-X25519",
	kyber512.Scheme(),
	hpke.KEM_X25519_HKDF_SHA256.Scheme(),
}

var kyber768X kem.Scheme = &scheme{
	"Kyber768-X448",
	kyber768.Scheme(),
	hpke.KEM_X448_HKDF_SHA512.Scheme(),
}

var kyber1024X kem.Scheme = &scheme{
	"Kyber1024-X448",
	kyber1024.Scheme(),
	hpke.KEM_X448_HKDF_SHA512.Scheme(),
}

// Public key of a hybrid KEM.
type publicKey struct {
	scheme *scheme
	first  kem.PublicKey
	second kem.PublicKey
}

// Private key of a hybrid KEM.
type privateKey struct {
	scheme *scheme
	first  kem.PrivateKey
	second kem.PrivateKey
}

// Scheme for a hybrid KEM.
type scheme struct {
	name   string
	first  kem.Scheme
	second kem.Scheme
}

func (sch *scheme) Name() string { return sch.name }
func (sch *scheme) PublicKeySize() int {
	return sch.first.PublicKeySize() + sch.second.PublicKeySize()
}

func (sch *scheme) PrivateKeySize() int {
	return sch.first.PrivateKeySize() + sch.second.PrivateKeySize()
}

func (sch *scheme) SeedSize() int {
	first := sch.first.SeedSize()
	second := sch.second.SeedSize()
	ret := second
	if first > second {
		ret = first
	}
	if ret > 32 {
		panic("SeedSize too big for SHAKE256")
	}
	return ret
}

func (sch *scheme) SharedKeySize() int {
	return sch.first.SharedKeySize() + sch.second.SharedKeySize()
}

func (sch *scheme) CiphertextSize() int {
	return sch.first.CiphertextSize() + sch.second.CiphertextSize()
}

func (sch *scheme) EncapsulationSeedSize() int {
	first := sch.first.EncapsulationSeedSize()
	second := sch.second.EncapsulationSeedSize()
	ret := second
	if first > second {
		ret = first
	}
	if ret > 32 {
		panic("EncapsulationSeedSize too big for SHAKE256")
	}
	return ret
}

func (sk *privateKey) Scheme() kem.Scheme { return sk.scheme }
func (pk *publicKey) Scheme() kem.Scheme  { return pk.scheme }

func (sk *privateKey) MarshalBinary() ([]byte, error) {
	if sk.first == nil || sk.second == nil {
		return nil, ErrUninitialized
	}
	first, err := sk.first.MarshalBinary()
	if err != nil {
		return nil, err
	}
	second, err := sk.second.MarshalBinary()
	if err != nil {
		return nil, err
	}
	return append(first, second...), nil
}

func (sk *privateKey) Equal(other kem.PrivateKey) bool {
	oth, ok := other.(*privateKey)
	if !ok {
		return false
	}
	if sk.first == nil && sk.second == nil && oth.first == nil && oth.second == nil {
		return true
	}
	if sk.first == nil || sk.second == nil || oth.first == nil || oth.second == nil {
		return false
	}
	return sk.first.Equal(oth.first) && sk.second.Equal(oth.second)
}

func (sk *privateKey) Public() kem.PublicKey {
	return &publicKey{sk.scheme, sk.first.Public(), sk.second.Public()}
}

func (pk *publicKey) Equal(other kem.PublicKey) bool {
	oth, ok := other.(*publicKey)
	if !ok {
		return false
	}
	if pk.first == nil && pk.second == nil && oth.first == nil && oth.second == nil {
		return true
	}
	if pk.first == nil || pk.second == nil || oth.first == nil || oth.second == nil {
		return false
	}
	return pk.first.Equal(oth.first) && pk.second.Equal(oth.second)
}

func (pk *publicKey) MarshalBinary() ([]byte, error) {
	if pk.first == nil || pk.second == nil {
		return nil, ErrUninitialized
	}
	first, err := pk.first.MarshalBinary()
	if err != nil {
		return nil, err
	}
	second, err := pk.second.MarshalBinary()
	if err != nil {
		return nil, err
	}
	return append(first, second...), nil
}

func (sch *scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) {
	pk1, sk1, err := sch.first.GenerateKeyPair()
	if err != nil {
		return nil, nil, err
	}
	pk2, sk2, err := sch.second.GenerateKeyPair()
	if err != nil {
		return nil, nil, err
	}

	return &publicKey{sch, pk1, pk2}, &privateKey{sch, sk1, sk2}, nil
}

func (sch *scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) {
	if len(seed) != sch.SeedSize() {
		panic(kem.ErrSeedSize)
	}
	h := sha3.NewShake256()
	_, _ = h.Write(seed)
	first := make([]byte, sch.first.SeedSize())
	second := make([]byte, sch.second.SeedSize())
	_, _ = h.Read(first)
	_, _ = h.Read(second)

	pk1, sk1 := sch.first.DeriveKeyPair(first)
	pk2, sk2 := sch.second.DeriveKeyPair(second)

	return &publicKey{sch, pk1, pk2}, &privateKey{sch, sk1, sk2}
}

func (sch *scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) {
	pub, ok := pk.(*publicKey)
	if !ok {
		return nil, nil, kem.ErrTypeMismatch
	}

	ct1, ss1, err := sch.first.Encapsulate(pub.first)
	if err != nil {
		return nil, nil, err
	}

	ct2, ss2, err := sch.second.Encapsulate(pub.second)
	if err != nil {
		return nil, nil, err
	}

	return append(ct1, ct2...), append(ss1, ss2...), nil
}

func (sch *scheme) EncapsulateDeterministically(
	pk kem.PublicKey, seed []byte,
) (ct, ss []byte, err error) {
	if len(seed) != sch.EncapsulationSeedSize() {
		return nil, nil, kem.ErrSeedSize
	}

	h := sha3.NewShake256()
	_, _ = h.Write(seed)
	first := make([]byte, sch.first.EncapsulationSeedSize())
	second := make([]byte, sch.second.EncapsulationSeedSize())
	_, _ = h.Read(first)
	_, _ = h.Read(second)

	pub, ok := pk.(*publicKey)
	if !ok {
		return nil, nil, kem.ErrTypeMismatch
	}

	ct1, ss1, err := sch.first.EncapsulateDeterministically(pub.first, first)
	if err != nil {
		return nil, nil, err
	}
	ct2, ss2, err := sch.second.EncapsulateDeterministically(pub.second, second)
	if err != nil {
		return nil, nil, err
	}
	return append(ct1, ct2...), append(ss1, ss2...), nil
}

func (sch *scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) {
	if len(ct) != sch.CiphertextSize() {
		return nil, kem.ErrCiphertextSize
	}

	priv, ok := sk.(*privateKey)
	if !ok {
		return nil, kem.ErrTypeMismatch
	}

	firstSize := sch.first.CiphertextSize()
	ss1, err := sch.first.Decapsulate(priv.first, ct[:firstSize])
	if err != nil {
		return nil, err
	}
	ss2, err := sch.second.Decapsulate(priv.second, ct[firstSize:])
	if err != nil {
		return nil, err
	}
	return append(ss1, ss2...), nil
}

func (sch *scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) {
	if len(buf) != sch.PublicKeySize() {
		return nil, kem.ErrPubKeySize
	}
	firstSize := sch.first.PublicKeySize()
	pk1, err := sch.first.UnmarshalBinaryPublicKey(buf[:firstSize])
	if err != nil {
		return nil, err
	}
	pk2, err := sch.second.UnmarshalBinaryPublicKey(buf[firstSize:])
	if err != nil {
		return nil, err
	}
	return &publicKey{sch, pk1, pk2}, nil
}

func (sch *scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) {
	if len(buf) != sch.PrivateKeySize() {
		return nil, kem.ErrPrivKeySize
	}
	firstSize := sch.first.PrivateKeySize()
	sk1, err := sch.first.UnmarshalBinaryPrivateKey(buf[:firstSize])
	if err != nil {
		return nil, err
	}
	sk2, err := sch.second.UnmarshalBinaryPrivateKey(buf[firstSize:])
	if err != nil {
		return nil, err
	}
	return &privateKey{sch, sk1, sk2}, nil
}
