package es256

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/sha256"
	"encoding/json"
)

var keyOpsSet = newStringSet("sign", "verify")

// PublicKey is a JSON marshallable ECDSA public key.
// Only supports P-256 at the moment.
type PublicKey struct {
	*ecdsa.PublicKey
	KeyID string
}

// Size implements jwt.Algorithm
func (e *PublicKey) Size() int {
	return 64
}

// Name implements jwt.Alogithm
func (e *PublicKey) Name() string {
	return "ES256"
}

// Validate implements jwt.Algorithm
func (e *PublicKey) Validate(value, rawSig []byte) error {
	sig := &signature{}
	if err := sig.Decode(rawSig); err != nil {
		return err
	}

	hashedVal := sha256.New()
	if _, err := hashedVal.Write(value); err != nil {
		return err
	}

	if ecdsa.Verify(e.PublicKey, hashedVal.Sum(nil), sig.R, sig.S) {
		return nil
	}

	return ErrInvalidSig
}

// MarshalJSON implements json.Marshaler
func (e *PublicKey) MarshalJSON() ([]byte, error) {
	return json.Marshal(struct {
		KeyType   string    `json:"kty"`
		Use       string    `json:"use"`
		Curve     string    `json:"crv"`
		KeyOps    stringSet `json:"key_ops"`
		Algorithm string    `json:"alg"`
		KeyID     string    `json:"kid"`
		X         *bigInt   `json:"x"`
		Y         *bigInt   `json:"y"`
	}{
		KeyType:   "EC",
		Use:       "sig",
		Curve:     "P-256",
		X:         &bigInt{Int: e.PublicKey.X},
		Y:         &bigInt{Int: e.PublicKey.Y},
		KeyOps:    *keyOpsSet,
		Algorithm: "ES256",
		KeyID:     e.KeyID,
	})
}

// UnmarshalJSON implements json.Unmarshaler
func (e *PublicKey) UnmarshalJSON(in []byte) error {
	var raw struct {
		KeyType   string    `json:"kty"`
		Use       string    `json:"use"`
		Curve     string    `json:"crv"`
		KeyOps    stringSet `json:"key_ops"`
		Algorithm string    `json:"alg"`
		KeyID     string    `json:"kid"`
		X         *bigInt   `json:"x"`
		Y         *bigInt   `json:"y"`
	}
	err := json.Unmarshal(in, &raw)
	if err != nil {
		return err
	}

	e.KeyID = raw.KeyID
	e.PublicKey = new(ecdsa.PublicKey)
	e.PublicKey.X = raw.X.Int
	e.PublicKey.Y = raw.Y.Int
	e.PublicKey.Curve = elliptic.P256()

	return nil
}
