package keys

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"io"

	"github.com/theupdateframework/go-tuf/data"
)

func init() {
	// Note: we use LoadOrStore here to prevent accidentally overriding the
	// an explicit deprecated ECDSA verifier.
	// TODO: When deprecated ECDSA is removed, this can switch back to Store.
	VerifierMap.LoadOrStore(data.KeyTypeECDSA_SHA2_P256_OLD_FMT, NewEcdsaVerifier)
	VerifierMap.LoadOrStore(data.KeyTypeECDSA_SHA2_P256, NewEcdsaVerifier)
	SignerMap.Store(data.KeyTypeECDSA_SHA2_P256_OLD_FMT, newEcdsaSigner)
	SignerMap.Store(data.KeyTypeECDSA_SHA2_P256, newEcdsaSigner)
}

func NewEcdsaVerifier() Verifier {
	return &EcdsaVerifier{}
}

func newEcdsaSigner() Signer {
	return &ecdsaSigner{}
}

type EcdsaVerifier struct {
	PublicKey *PKIXPublicKey `json:"public"`
	ecdsaKey  *ecdsa.PublicKey
	key       *data.PublicKey
}

func (p *EcdsaVerifier) Public() string {
	// This is already verified to succeed when unmarshalling a public key.
	r, err := x509.MarshalPKIXPublicKey(p.ecdsaKey)
	if err != nil {
		// TODO: Gracefully handle these errors.
		// See https://github.com/theupdateframework/go-tuf/issues/363
		panic(err)
	}
	return string(r)
}

func (p *EcdsaVerifier) Verify(msg, sigBytes []byte) error {
	hash := sha256.Sum256(msg)

	if !ecdsa.VerifyASN1(p.ecdsaKey, hash[:], sigBytes) {
		return errors.New("tuf: ecdsa signature verification failed")
	}
	return nil
}

func (p *EcdsaVerifier) MarshalPublicKey() *data.PublicKey {
	return p.key
}

func (p *EcdsaVerifier) UnmarshalPublicKey(key *data.PublicKey) error {
	// Prepare decoder limited to 512Kb
	dec := json.NewDecoder(io.LimitReader(bytes.NewReader(key.Value), MaxJSONKeySize))

	// Unmarshal key value
	if err := dec.Decode(p); err != nil {
		if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
			return fmt.Errorf("tuf: the public key is truncated or too large: %w", err)
		}
		return err
	}

	ecdsaKey, ok := p.PublicKey.PublicKey.(*ecdsa.PublicKey)
	if !ok {
		return fmt.Errorf("invalid public key")
	}

	if _, err := x509.MarshalPKIXPublicKey(ecdsaKey); err != nil {
		return fmt.Errorf("marshalling to PKIX key: invalid public key")
	}

	p.ecdsaKey = ecdsaKey
	p.key = key
	return nil
}

type ecdsaSigner struct {
	*ecdsa.PrivateKey
}

type ecdsaPrivateKeyValue struct {
	Private string         `json:"private"`
	Public  *PKIXPublicKey `json:"public"`
}

func (s *ecdsaSigner) PublicData() *data.PublicKey {
	// This uses a trusted public key JSON format with a trusted Public value.
	keyValBytes, _ := json.Marshal(EcdsaVerifier{PublicKey: &PKIXPublicKey{PublicKey: s.Public()}})
	return &data.PublicKey{
		Type:       data.KeyTypeECDSA_SHA2_P256,
		Scheme:     data.KeySchemeECDSA_SHA2_P256,
		Algorithms: data.HashAlgorithms,
		Value:      keyValBytes,
	}
}

func (s *ecdsaSigner) SignMessage(message []byte) ([]byte, error) {
	hash := sha256.Sum256(message)
	return ecdsa.SignASN1(rand.Reader, s.PrivateKey, hash[:])
}

func (s *ecdsaSigner) MarshalPrivateKey() (*data.PrivateKey, error) {
	priv, err := x509.MarshalECPrivateKey(s.PrivateKey)
	if err != nil {
		return nil, err
	}
	pemKey := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: priv})
	val, err := json.Marshal(ecdsaPrivateKeyValue{
		Private: string(pemKey),
		Public:  &PKIXPublicKey{PublicKey: s.Public()},
	})
	if err != nil {
		return nil, err
	}
	return &data.PrivateKey{
		Type:       data.KeyTypeECDSA_SHA2_P256,
		Scheme:     data.KeySchemeECDSA_SHA2_P256,
		Algorithms: data.HashAlgorithms,
		Value:      val,
	}, nil
}

func (s *ecdsaSigner) UnmarshalPrivateKey(key *data.PrivateKey) error {
	val := ecdsaPrivateKeyValue{}
	if err := json.Unmarshal(key.Value, &val); err != nil {
		return err
	}
	block, _ := pem.Decode([]byte(val.Private))
	if block == nil {
		return errors.New("invalid PEM value")
	}
	if block.Type != "EC PRIVATE KEY" {
		return fmt.Errorf("invalid block type: %s", block.Type)
	}
	k, err := x509.ParseECPrivateKey(block.Bytes)
	if err != nil {
		return err
	}
	if k.Curve != elliptic.P256() {
		return errors.New("unsupported ecdsa curve")
	}
	if _, err := json.Marshal(EcdsaVerifier{
		PublicKey: &PKIXPublicKey{PublicKey: k.Public()}}); err != nil {
		return fmt.Errorf("invalid public key: %s", err)
	}

	s.PrivateKey = k
	return nil
}

func GenerateEcdsaKey() (*ecdsaSigner, error) {
	privkey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, err
	}
	return &ecdsaSigner{privkey}, nil
}
