package jwe

import (
	"github.com/lestrrat-go/iter/mapiter"
	"github.com/lestrrat-go/jwx/v2/internal/iter"
	"github.com/lestrrat-go/jwx/v2/jwa"
	"github.com/lestrrat-go/jwx/v2/jwe/internal/keygen"
)

// KeyEncrypter is an interface for object that can encrypt a
// content encryption key.
//
// You can use this in place of a regular key (i.e. in jwe.WithKey())
// to encrypt the content encryption key in a JWE message without
// having to expose the secret key in memory, for example, when you
// want to use hardware security modules (HSMs) to encrypt the key.
//
// This API is experimental and may change without notice, even
// in minor releases.
type KeyEncrypter interface {
	// Algorithm returns the algorithm used to encrypt the key.
	Algorithm() jwa.KeyEncryptionAlgorithm

	// EncryptKey encrypts the given content encryption key.
	EncryptKey([]byte) ([]byte, error)
}

// KeyIDer is an interface for things that can return a key ID.
//
// As of this writing, this is solely used to identify KeyEncrypter
// objects that also carry a key ID on its own.
type KeyIDer interface {
	KeyID() string
}

// KeyDecrypter is an interface for objects that can decrypt a content
// encryption key.
//
// You can use this in place of a regular key (i.e. in jwe.WithKey())
// to decrypt the encrypted key in a JWE message without having to
// expose the secret key in memory, for example, when you want to use
// hardware security modules (HSMs) to decrypt the key.
//
// This API is experimental and may change without notice, even
// in minor releases.
type KeyDecrypter interface {
	// Decrypt decrypts the encrypted key of a JWE message.
	//
	// Make sure you understand how JWE messages are structured.
	//
	// For example, while in most circumstances a JWE message will only have one recipient,
	// a JWE message may contain multiple recipients, each with their own
	// encrypted key. This method will be called for each recipient, instead of
	// just once for a message.
	//
	// Also, header values could be found in either protected/unprotected headers
	// of a JWE message, as well as in protected/unprotected headers for each recipient.
	// When checking a header value, you can decide to use either one, or both, but you
	// must be aware that there are multiple places to look for.
	DecryptKey(alg jwa.KeyEncryptionAlgorithm, encryptedKey []byte, recipient Recipient, message *Message) ([]byte, error)
}

// Recipient holds the encrypted key and hints to decrypt the key
type Recipient interface {
	Headers() Headers
	EncryptedKey() []byte
	SetHeaders(Headers) error
	SetEncryptedKey([]byte) error
}

type stdRecipient struct {
	// Comments on each field are taken from https://datatracker.ietf.org/doc/html/rfc7516
	//
	// header
	//    The "header" member MUST be present and contain the value JWE Per-
	//    Recipient Unprotected Header when the JWE Per-Recipient
	//    Unprotected Header value is non-empty; otherwise, it MUST be
	//    absent.  This value is represented as an unencoded JSON object,
	//    rather than as a string.  These Header Parameter values are not
	//    integrity protected.
	//
	// At least one of the "header", "protected", and "unprotected" members
	// MUST be present so that "alg" and "enc" Header Parameter values are
	// conveyed for each recipient computation.
	//
	// JWX note: see Message.unprotectedHeaders
	headers Headers

	// encrypted_key
	//    The "encrypted_key" member MUST be present and contain the value
	//    BASE64URL(JWE Encrypted Key) when the JWE Encrypted Key value is
	//    non-empty; otherwise, it MUST be absent.
	encryptedKey []byte
}

// Message contains the entire encrypted JWE message. You should not
// expect to use Message for anything other than inspecting the
// state of an encrypted message. This is because encryption is
// highly context-sensitive, and once we parse the original payload
// into an object, we may not always be able to recreate the exact
// context in which the encryption happened.
//
// For example, it is totally valid for if the protected header's
// integrity was calculated using a non-standard line breaks:
//
//	{"a dummy":
//	  "protected header"}
//
// Once parsed, though, we can only serialize the protected header as:
//
//	{"a dummy":"protected header"}
//
// which would obviously result in a contradicting integrity value
// if we tried to re-calculate it from a parsed message.
//
//nolint:govet
type Message struct {
	// Comments on each field are taken from https://datatracker.ietf.org/doc/html/rfc7516
	//
	// protected
	//    The "protected" member MUST be present and contain the value
	//    BASE64URL(UTF8(JWE Protected Header)) when the JWE Protected
	//    Header value is non-empty; otherwise, it MUST be absent.  These
	//    Header Parameter values are integrity protected.
	protectedHeaders Headers

	// unprotected
	//    The "unprotected" member MUST be present and contain the value JWE
	//    Shared Unprotected Header when the JWE Shared Unprotected Header
	//    value is non-empty; otherwise, it MUST be absent.  This value is
	//    represented as an unencoded JSON object, rather than as a string.
	//    These Header Parameter values are not integrity protected.
	//
	// JWX note: This field is NOT mutually exclusive with per-recipient
	// headers within the implementation because... it's too much work.
	// It is _never_ populated (we don't provide a way to do this) upon encryption.
	// When decrypting, if present its values are always merged with
	// per-recipient header.
	unprotectedHeaders Headers

	// iv
	//    The "iv" member MUST be present and contain the value
	//    BASE64URL(JWE Initialization Vector) when the JWE Initialization
	//    Vector value is non-empty; otherwise, it MUST be absent.
	initializationVector []byte

	// aad
	//    The "aad" member MUST be present and contain the value
	//    BASE64URL(JWE AAD)) when the JWE AAD value is non-empty;
	//    otherwise, it MUST be absent.  A JWE AAD value can be included to
	//    supply a base64url-encoded value to be integrity protected but not
	//    encrypted.
	authenticatedData []byte

	// ciphertext
	//    The "ciphertext" member MUST be present and contain the value
	//    BASE64URL(JWE Ciphertext).
	cipherText []byte

	// tag
	//    The "tag" member MUST be present and contain the value
	//    BASE64URL(JWE Authentication Tag) when the JWE Authentication Tag
	//    value is non-empty; otherwise, it MUST be absent.
	tag []byte

	// recipients
	//    The "recipients" member value MUST be an array of JSON objects.
	//    Each object contains information specific to a single recipient.
	//    This member MUST be present with exactly one array element per
	//    recipient, even if some or all of the array element values are the
	//    empty JSON object "{}" (which can happen when all Header Parameter
	//    values are shared between all recipients and when no encrypted key
	//    is used, such as when doing Direct Encryption).
	//
	// Some Header Parameters, including the "alg" parameter, can be shared
	// among all recipient computations.  Header Parameters in the JWE
	// Protected Header and JWE Shared Unprotected Header values are shared
	// among all recipients.
	//
	// The Header Parameter values used when creating or validating per-
	// recipient ciphertext and Authentication Tag values are the union of
	// the three sets of Header Parameter values that may be present: (1)
	// the JWE Protected Header represented in the "protected" member, (2)
	// the JWE Shared Unprotected Header represented in the "unprotected"
	// member, and (3) the JWE Per-Recipient Unprotected Header represented
	// in the "header" member of the recipient's array element.  The union
	// of these sets of Header Parameters comprises the JOSE Header.  The
	// Header Parameter names in the three locations MUST be disjoint.
	recipients []Recipient

	// TODO: Additional members can be present in both the JSON objects defined
	// above; if not understood by implementations encountering them, they
	// MUST be ignored.
	// privateParams map[string]interface{}

	// These two fields below are not available for the public consumers of this object.
	// rawProtectedHeaders stores the original protected header buffer
	rawProtectedHeaders []byte
	// storeProtectedHeaders is a hint to be used in UnmarshalJSON().
	// When this flag is true, UnmarshalJSON() will populate the
	// rawProtectedHeaders field
	storeProtectedHeaders bool
}

// populater is an interface for things that may modify the
// JWE header. e.g. ByteWithECPrivateKey
type populater interface {
	Populate(keygen.Setter) error
}

type Visitor = iter.MapVisitor
type VisitorFunc = iter.MapVisitorFunc
type HeaderPair = mapiter.Pair
type Iterator = mapiter.Iterator
