package sign

import (
	"crypto/ed25519"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"strings"
)

// GenerateKey creates a fresh Ed25519 keypair for offline use.
func GenerateKey() (ed25519.PublicKey, ed25519.PrivateKey, error) {
	return ed25519.GenerateKey(rand.Reader)
}

// EncodePrivate / EncodePublic render keys as base64 (std) for storage. The
// private encoding is meant to be written to an OFFLINE medium only.
func EncodePrivate(priv ed25519.PrivateKey) string {
	return base64.StdEncoding.EncodeToString(priv)
}

func EncodePublic(pub ed25519.PublicKey) string {
	return base64.StdEncoding.EncodeToString(pub)
}

// DecodePrivate parses a base64-encoded Ed25519 private key.
func DecodePrivate(s string) (ed25519.PrivateKey, error) {
	b, err := base64.StdEncoding.DecodeString(strings.TrimSpace(s))
	if err != nil {
		return nil, fmt.Errorf("sign: private key not valid base64: %w", err)
	}
	if len(b) != ed25519.PrivateKeySize {
		return nil, fmt.Errorf("sign: private key wrong size: got %d want %d", len(b), ed25519.PrivateKeySize)
	}
	return ed25519.PrivateKey(b), nil
}

// DecodePublic parses a base64-encoded Ed25519 public key.
func DecodePublic(s string) (ed25519.PublicKey, error) {
	b, err := base64.StdEncoding.DecodeString(strings.TrimSpace(s))
	if err != nil {
		return nil, fmt.Errorf("sign: public key not valid base64: %w", err)
	}
	if len(b) != ed25519.PublicKeySize {
		return nil, fmt.Errorf("sign: public key wrong size: got %d want %d", len(b), ed25519.PublicKeySize)
	}
	return ed25519.PublicKey(b), nil
}

// ParseKeyRing builds a KeyRing from "keyid=base64pub" specs. Multiple specs
// (comma- or repeat-supplied) enable a rotation window where either key
// validates a document.
func ParseKeyRing(specs []string) (KeyRing, error) {
	ring := KeyRing{}
	for _, spec := range specs {
		for _, part := range strings.Split(spec, ",") {
			part = strings.TrimSpace(part)
			if part == "" {
				continue
			}
			id, b64, ok := strings.Cut(part, "=")
			if !ok {
				return nil, fmt.Errorf("sign: key ring spec %q must be keyid=base64pubkey", part)
			}
			pub, err := DecodePublic(b64)
			if err != nil {
				return nil, err
			}
			ring[strings.TrimSpace(id)] = pub
		}
	}
	if len(ring) == 0 {
		return nil, fmt.Errorf("sign: empty key ring")
	}
	return ring, nil
}
