package identity

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"math/big"
	"os"
)

var (
	// DefaultSigningCurve is the default elliptic curve used for signing.
	DefaultSigningCurve = elliptic.P384
)

func LoadSigningKeyFromFile(filePath string) (*ecdsa.PrivateKey, error) {
	masterKeyFile, err := os.ReadFile(filePath)
	if err != nil {
		return nil, err
	}

	// The second argument is not an error.
	derEncoded, _ := pem.Decode(masterKeyFile)
	if derEncoded == nil {
		return nil, fmt.Errorf("failed to decode master key file")
	}

	masterKey, err := x509.ParseECPrivateKey(derEncoded.Bytes)
	if err != nil {
		return nil, err
	}

	return masterKey, nil
}

// GenerateSigningKey generates a new ECDSA private key for signing.
func GenerateSigningKey() (*ecdsa.PrivateKey, error) {
	return ecdsa.GenerateKey(DefaultSigningCurve(), rand.Reader)
}

// Sign signs the data with the private key.
func Sign(priv *ecdsa.PrivateKey, data []byte) ([]byte, error) {
	return ecdsa.SignASN1(rand.Reader, priv, data)
}

// Verify verifies the signature of the data with the public key.
func Verify(pub *ecdsa.PublicKey, data, signature []byte) bool {
	return ecdsa.VerifyASN1(pub, data, signature)
}

// ExportPublicSigningKey exports the public key as a byte slice.
func ExportPublicSigningKey(pub *ecdsa.PublicKey) ([]byte, error) {
	data := struct {
		N    []byte `json:"n"`
		PubX []byte `json:"pub_x"`
		PubY []byte `json:"pub_y"`
	}{
		N:    pub.Curve.Params().N.Bytes(),
		PubX: pub.X.Bytes(),
		PubY: pub.Y.Bytes(),
	}
	return json.Marshal(data)
}

// ImportPublicSigningKey imports the public key from a byte slice.
func ImportPublicSigningKey(pubBytes []byte) (*ecdsa.PublicKey, error) {
	var data struct {
		N    []byte `json:"n"`
		PubX []byte `json:"pub_x"`
		PubY []byte `json:"pub_y"`
	}
	if err := json.Unmarshal(pubBytes, &data); err != nil {
		return nil, err
	}

	params := new(ecdsa.PublicKey)
	params.Curve = DefaultSigningCurve()
	params.X = new(big.Int).SetBytes(data.PubX)
	params.Y = new(big.Int).SetBytes(data.PubY)

	return params, nil
}