package jwk

import (
	"crypto"
	"encoding/json"
	"fmt"

	"github.com/lestrrat-go/jwx/internal/base64"
	"github.com/lestrrat-go/jwx/jwa"
	pdebug "github.com/lestrrat-go/pdebug"
	"github.com/pkg/errors"
)

func newSymmetricKey(key []byte) (*SymmetricKey, error) {
	if len(key) == 0 {
		return nil, errors.New(`non-empty []byte key required`)
	}

	var hdr StandardHeaders
	hdr.Set(KeyTypeKey, jwa.OctetSeq)
	return &SymmetricKey{
		headers: &hdr,
		key:     key,
	}, nil
}

// Materialize returns the octets for this symmetric key.
// Since this is a symmetric key, this just calls Octets
func (s SymmetricKey) Materialize() (interface{}, error) {
	return s.Octets(), nil
}

// Octets returns the octets in the key
func (s SymmetricKey) Octets() []byte {
	return s.key
}

// Thumbprint returns the JWK thumbprint using the indicated
// hashing algorithm, according to RFC 7638
func (s SymmetricKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
	h := hash.New()
	fmt.Fprintf(h, `{"k":"`)
	fmt.Fprintf(h, base64.EncodeToString(s.key))
	fmt.Fprintf(h, `","kty":"oct"}`)
	return h.Sum(nil), nil
}

func (k *SymmetricKey) UnmarshalJSON(data []byte) (err error) {
	if pdebug.Enabled {
		g := pdebug.Marker("jwk.SymmetricKey.UnmarshalJSON").BindError(&err)
		defer g.End()
	}

	m := map[string]interface{}{}
	if err := json.Unmarshal(data, &m); err != nil {
		return errors.Wrap(err, `failed to unmarshal public key`)
	}

	if err := k.ExtractMap(m); err != nil {
		return errors.Wrap(err, `failed to extract data from map`)
	}
	return nil
}

func (s *SymmetricKey) ExtractMap(m map[string]interface{}) (err error) {
	if pdebug.Enabled {
		g := pdebug.Marker("jwk.SymmetricKey.ExtractMap").BindError(&err)
		defer g.End()
	}

	const kKey = `k`

	kbuf, err := getRequiredKey(m, kKey)
	if err != nil {
		return errors.Wrapf(err, `failed to get required key '%s'`, kKey)
	}
	delete(m, kKey)

	var hdrs StandardHeaders
	if err := hdrs.ExtractMap(m); err != nil {
		return errors.Wrap(err, `failed to extract header values`)
	}

	*s = SymmetricKey{
		headers: &hdrs,
		key:     kbuf,
	}
	return nil
}

func (s SymmetricKey) MarshalJSON() (buf []byte, err error) {
	if pdebug.Enabled {
		g := pdebug.Marker("jwk.SymmetricKey.MarshalJSON").BindError(&err)
		defer g.End()
	}

	m := make(map[string]interface{})
	if err := s.PopulateMap(m); err != nil {
		return nil, errors.Wrap(err, `failed to populate symmetric key values`)
	}

	return json.Marshal(m)
}

func (s SymmetricKey) PopulateMap(m map[string]interface{}) (err error) {
	if pdebug.Enabled {
		g := pdebug.Marker("jwk.SymmetricKey.PopulateMap").BindError(&err)
		defer g.End()
	}

	if err := s.headers.PopulateMap(m); err != nil {
		return errors.Wrap(err, `failed to populate header values`)
	}

	const kKey = `k`
	m[kKey] = base64.EncodeToString(s.key)
	return nil
}
