package jwk

import (
	"crypto/rsa"
	"encoding/binary"
	"errors"
	"fmt"
	"math/big"

	"github.com/open-policy-agent/opa/internal/jwx/jwa"
)

func newRSAPublicKey(key *rsa.PublicKey) (*RSAPublicKey, error) {

	var hdr StandardHeaders
	err := hdr.Set(KeyTypeKey, jwa.RSA)
	if err != nil {
		return nil, fmt.Errorf("failed to set Key Type: %w", err)
	}
	return &RSAPublicKey{
		StandardHeaders: &hdr,
		key:             key,
	}, nil
}

func newRSAPrivateKey(key *rsa.PrivateKey) (*RSAPrivateKey, error) {

	var hdr StandardHeaders
	err := hdr.Set(KeyTypeKey, jwa.RSA)
	if err != nil {
		return nil, fmt.Errorf("failed to set Key Type: %w", err)
	}

	var algoParams jwa.AlgorithmParameters

	// it is needed to use raw encoding to omit the "=" paddings at the end
	algoParams.D = key.D.Bytes()
	algoParams.P = key.Primes[0].Bytes()
	algoParams.Q = key.Primes[1].Bytes()
	algoParams.Dp = key.Precomputed.Dp.Bytes()
	algoParams.Dq = key.Precomputed.Dq.Bytes()
	algoParams.Qi = key.Precomputed.Qinv.Bytes()

	// "modulus" (N) from the public key in the private key
	algoParams.N = key.PublicKey.N.Bytes()

	// make the E a.k.a "coprime"
	// https://en.wikipedia.org/wiki/RSA_(cryptosystem)
	coprime := make([]byte, 8)
	binary.BigEndian.PutUint64(coprime, uint64(key.PublicKey.E))
	// find the 1st index of non 0x0 paddings from the beginning
	i := 0
	for ; i < len(coprime); i++ {
		if coprime[i] != 0x0 {
			break
		}
	}
	algoParams.E = coprime[i:]

	return &RSAPrivateKey{
		StandardHeaders:     &hdr,
		AlgorithmParameters: &algoParams,
		key:                 key,
	}, nil
}

// Materialize returns the standard RSA Public Key representation stored in the internal representation
func (k *RSAPublicKey) Materialize() (interface{}, error) {
	if k.key == nil {
		return nil, errors.New("key has no rsa.PublicKey associated with it")
	}
	return k.key, nil
}

// Materialize returns the standard RSA Private Key representation stored in the internal representation
func (k *RSAPrivateKey) Materialize() (interface{}, error) {
	if k.key == nil {
		return nil, errors.New("key has no rsa.PrivateKey associated with it")
	}
	return k.key, nil
}

// GenerateKey creates a RSAPublicKey from a RawKeyJSON
func (k *RSAPublicKey) GenerateKey(keyJSON *RawKeyJSON) error {

	if keyJSON.N == nil || keyJSON.E == nil {
		return errors.New("missing mandatory key parameters N or E")
	}
	rsaPublicKey := &rsa.PublicKey{
		N: (&big.Int{}).SetBytes(keyJSON.N.Bytes()),
		E: int((&big.Int{}).SetBytes(keyJSON.E.Bytes()).Int64()),
	}
	k.key = rsaPublicKey
	k.StandardHeaders = &keyJSON.StandardHeaders
	return nil
}

// GenerateKey creates a RSAPublicKey from a RawKeyJSON
func (k *RSAPrivateKey) GenerateKey(keyJSON *RawKeyJSON) error {

	rsaPublicKey := &RSAPublicKey{}
	err := rsaPublicKey.GenerateKey(keyJSON)
	if err != nil {
		return fmt.Errorf("failed to generate public key: %w", err)
	}

	if keyJSON.D == nil || keyJSON.P == nil || keyJSON.Q == nil {
		return errors.New("missing mandatory key parameters D, P or Q")
	}
	privateKey := &rsa.PrivateKey{
		PublicKey: *rsaPublicKey.key,
		D:         (&big.Int{}).SetBytes(keyJSON.D.Bytes()),
		Primes: []*big.Int{
			(&big.Int{}).SetBytes(keyJSON.P.Bytes()),
			(&big.Int{}).SetBytes(keyJSON.Q.Bytes()),
		},
	}

	if keyJSON.Dp.Len() > 0 {
		privateKey.Precomputed.Dp = (&big.Int{}).SetBytes(keyJSON.Dp.Bytes())
	}
	if keyJSON.Dq.Len() > 0 {
		privateKey.Precomputed.Dq = (&big.Int{}).SetBytes(keyJSON.Dq.Bytes())
	}
	if keyJSON.Qi.Len() > 0 {
		privateKey.Precomputed.Qinv = (&big.Int{}).SetBytes(keyJSON.Qi.Bytes())
	}

	k.key = privateKey
	k.StandardHeaders = &keyJSON.StandardHeaders
	k.AlgorithmParameters = &keyJSON.AlgorithmParameters
	return nil
}
