package authz

import (
	"crypto/ed25519"
	"crypto/rand"
	"crypto/sha256"
	"encoding/binary"
	"encoding/pem"
	"testing"
	"time"

	"golang.org/x/crypto/ssh"
)

// Test helpers that MINT armored SSHSIGs in-Go (hermetic) — the inverse of the
// production framing. They reuse the production signedData()/sshsigBlob so a test
// can never drift from the verifier's notion of the signed bytes.

// canonicalBlob delegates to the production CanonicalBlob (so the in-Go test minting can never
// drift from the real signed-bytes path). Panics on a params error — tests pass valid JSON.
func canonicalBlob(op, hostID, guestID, keyID, nonce, paramsJSON string, issued, expires time.Time) []byte {
	b, err := CanonicalBlob(op, hostID, guestID, keyID, nonce, paramsJSON, issued, expires)
	if err != nil {
		panic(err)
	}
	return b
}

// mintArmor builds an armored SSHSIG over message, using sign to produce the inner
// ssh.Signature over the recomputed SSHSIG signed-data.
func mintArmor(t *testing.T, pubMarshaled []byte, namespace, hashName string, message []byte, sign func([]byte) ssh.Signature) []byte {
	t.Helper()
	sb := &sshsigBlob{Version: 1, PublicKey: string(pubMarshaled), Namespace: namespace, Reserved: "", HashAlgo: hashName}
	signed, err := signedData(sb, message)
	if err != nil {
		t.Fatalf("signedData: %v", err)
	}
	sig := sign(signed)
	sb.Signature = string(ssh.Marshal(&sig))
	raw := append([]byte(sshsigMagic), ssh.Marshal(sb)...)
	return pem.EncodeToMemory(&pem.Block{Type: "SSH SIGNATURE", Bytes: raw})
}

// newEd25519Signer returns an ssh.PublicKey + a sign closure for a fresh ed25519 key.
func newEd25519Signer(t *testing.T) (ssh.PublicKey, func([]byte) ssh.Signature) {
	t.Helper()
	pub, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	sshPub, err := ssh.NewPublicKey(pub)
	if err != nil {
		t.Fatal(err)
	}
	sign := func(signed []byte) ssh.Signature {
		return ssh.Signature{Format: ssh.KeyAlgoED25519, Blob: ed25519.Sign(priv, signed)}
	}
	return sshPub, sign
}

// newSyntheticSKSigner emulates a FIDO2 sk-ssh-ed25519@openssh.com key with NO
// hardware (Phase 4 §5). It builds a spec-faithful sk public key and an sk-format
// signature: ed25519 over sha256(application)‖flags‖counter‖sha256(signed_data),
// sig.Blob = the raw ed25519 signature, sig.Rest = flags‖counter. It must verify
// through the UNCHANGED Verify path.
func newSyntheticSKSigner(t *testing.T) (ssh.PublicKey, func([]byte) ssh.Signature) {
	t.Helper()
	edPub, edPriv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	const application = "ssh:"
	skBlob := ssh.Marshal(struct {
		Name        string
		KeyBytes    []byte
		Application string
	}{"sk-ssh-ed25519@openssh.com", []byte(edPub), application})
	skPub, err := ssh.ParsePublicKey(skBlob)
	if err != nil {
		t.Fatalf("parse synthetic sk pubkey: %v", err)
	}
	if skPub.Type() != "sk-ssh-ed25519@openssh.com" {
		t.Fatalf("sk pubkey type = %q", skPub.Type())
	}

	sign := func(signed []byte) ssh.Signature {
		const flagUserPresence = byte(0x01) // required, else Verify rejects
		const counter = uint32(1)
		appDigest := sha256.Sum256([]byte(application))
		dataDigest := sha256.Sum256(signed)
		// original = appDigest ‖ flags ‖ counter(BE) ‖ dataDigest (x/crypto layout)
		var original []byte
		original = append(original, appDigest[:]...)
		original = append(original, flagUserPresence)
		original = binary.BigEndian.AppendUint32(original, counter)
		original = append(original, dataDigest[:]...)
		edSig := ed25519.Sign(edPriv, original)
		// sig.Rest = skFields{Flags, Counter} = flags ‖ counter(BE)
		rest := append([]byte{flagUserPresence}, binary.BigEndian.AppendUint32(nil, counter)...)
		return ssh.Signature{Format: "sk-ssh-ed25519@openssh.com", Blob: edSig, Rest: rest}
	}
	return skPub, sign
}
