package updater

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"errors"
	"strings"
	"testing"
)

// generateTestKey produces an ECDSA P-256 keypair in PEM form,
// matching the shape `cosign generate-key-pair` emits for the public
// half. The private half stays in-test for signing.
func generateTestKey(t *testing.T) (privKey *ecdsa.PrivateKey, pubPEM string) {
	t.Helper()
	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatalf("generate key: %v", err)
	}
	der, err := x509.MarshalPKIXPublicKey(&priv.PublicKey)
	if err != nil {
		t.Fatalf("marshal public key: %v", err)
	}
	pubPEM = string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der}))
	return priv, pubPEM
}

// signBlob mimics `cosign sign-blob`'s output: base64-encoded ASN.1-DER
// ECDSA signature over SHA256(body).
func signBlob(t *testing.T, priv *ecdsa.PrivateKey, body []byte) string {
	t.Helper()
	digest := sha256.Sum256(body)
	sig, err := ecdsa.SignASN1(rand.Reader, priv, digest[:])
	if err != nil {
		t.Fatalf("sign: %v", err)
	}
	return base64.StdEncoding.EncodeToString(sig)
}

func TestVerifyBlobSignaturePlaceholderRefuses(t *testing.T) {
	// A build that hasn't replaced the placeholder key must refuse
	// every verify call with ErrSignatureRequired so an un-rotated
	// build can't silently accept anything. Swap the embedded key
	// out for the placeholder shape and assert that.
	prev := BangerReleasePublicKey
	BangerReleasePublicKey = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPLACEHOLDER0000000000000000000
000000000000000000000000000000000000000000000000000000000000PLACE
-----END PUBLIC KEY-----`
	defer func() { BangerReleasePublicKey = prev }()
	err := VerifyBlobSignature([]byte("body"), []byte("sig"))
	if !errors.Is(err, ErrSignatureRequired) {
		t.Fatalf("err = %v, want ErrSignatureRequired", err)
	}
}

func TestVerifyBlobSignatureHappyPath(t *testing.T) {
	priv, pubPEM := generateTestKey(t)
	prev := BangerReleasePublicKey
	BangerReleasePublicKey = pubPEM
	defer func() { BangerReleasePublicKey = prev }()

	body := []byte("SHA256SUMS body bytes")
	sig := signBlob(t, priv, body)
	if err := VerifyBlobSignature(body, []byte(sig)); err != nil {
		t.Fatalf("VerifyBlobSignature: %v", err)
	}
}

func TestVerifyBlobSignatureRejectsTamperedBody(t *testing.T) {
	priv, pubPEM := generateTestKey(t)
	prev := BangerReleasePublicKey
	BangerReleasePublicKey = pubPEM
	defer func() { BangerReleasePublicKey = prev }()

	body := []byte("original body")
	sig := signBlob(t, priv, body)
	tampered := []byte("tampered body")
	err := VerifyBlobSignature(tampered, []byte(sig))
	if err == nil || !strings.Contains(err.Error(), "does not verify") {
		t.Fatalf("err = %v, want signature-mismatch", err)
	}
}

func TestVerifyBlobSignatureRejectsWrongKey(t *testing.T) {
	// Sign with one key, verify with a different one.
	signingPriv, _ := generateTestKey(t)
	_, otherPubPEM := generateTestKey(t)
	prev := BangerReleasePublicKey
	BangerReleasePublicKey = otherPubPEM
	defer func() { BangerReleasePublicKey = prev }()

	body := []byte("body")
	sig := signBlob(t, signingPriv, body)
	err := VerifyBlobSignature(body, []byte(sig))
	if err == nil || !strings.Contains(err.Error(), "does not verify") {
		t.Fatalf("err = %v, want wrong-key rejection", err)
	}
}

func TestVerifyBlobSignatureRejectsMalformed(t *testing.T) {
	_, pubPEM := generateTestKey(t)
	prev := BangerReleasePublicKey
	BangerReleasePublicKey = pubPEM
	defer func() { BangerReleasePublicKey = prev }()
	for _, tc := range []struct {
		name string
		sig  string
	}{
		{name: "not_base64", sig: "!!!not_b64!!!"},
		{name: "empty", sig: ""},
		{name: "garbage_bytes", sig: base64.StdEncoding.EncodeToString([]byte{0x01, 0x02, 0x03})},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			err := VerifyBlobSignature([]byte("body"), []byte(tc.sig))
			if err == nil {
				t.Fatalf("expected error for %s; got success", tc.name)
			}
		})
	}
}
