package signedjobs

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

	"gitea.dooplex.hu/admin/felhom-agent/internal/authz"
	"gitea.dooplex.hu/admin/felhom-agent/internal/hub"
	"golang.org/x/crypto/ssh"
)

// In-test SSHSIG minter (mirrors internal/reconcile/mint_test.go's framing) so the runner tests
// exercise the REAL authz.Verifier + reconcile.Gate over genuinely-signed blobs — a positive case
// verifying proves the framing, and the adversarial cases (non-pinned/replay/expired/retarget/
// forged) exercise the real rejection path end-to-end. Production authz stays verify-only.

const sshsigMagic = "SSHSIG"

type sshsigBlob struct {
	Version   uint32
	PublicKey string
	Namespace string
	Reserved  string
	HashAlgo  string
	Signature string
}

func signedDataForTest(ns string, msg []byte) []byte {
	h := sha256Sum(msg)
	body := ssh.Marshal(struct {
		Namespace string
		Reserved  string
		HashAlgo  string
		Hash      []byte
	}{ns, "", "sha256", h})
	return append([]byte(sshsigMagic), body...)
}

func mintArmor(pubMarshaled []byte, namespace string, message []byte, sign func([]byte) ssh.Signature) []byte {
	sb := &sshsigBlob{Version: 1, PublicKey: string(pubMarshaled), Namespace: namespace, Reserved: "", HashAlgo: "sha256"}
	sig := sign(signedDataForTest(namespace, message))
	sb.Signature = string(ssh.Marshal(&sig))
	raw := append([]byte(sshsigMagic), ssh.Marshal(sb)...)
	return pem.EncodeToMemory(&pem.Block{Type: "SSH SIGNATURE", Bytes: raw})
}

type testSigner struct {
	pub  ssh.PublicKey
	line string
	sign func([]byte) ssh.Signature
}

func newTestSigner(t *testing.T) testSigner {
	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)
	}
	return testSigner{
		pub:  sshPub,
		line: string(ssh.MarshalAuthorizedKey(sshPub)),
		sign: func(d []byte) ssh.Signature {
			return ssh.Signature{Format: ssh.KeyAlgoED25519, Blob: ed25519.Sign(priv, d)}
		},
	}
}

func (s testSigner) allowed(t *testing.T, keyID string, role authz.KeyRole) authz.AllowedSigner {
	t.Helper()
	as, err := authz.NewAllowedSigner(keyID, role, s.line)
	if err != nil {
		t.Fatalf("NewAllowedSigner: %v", err)
	}
	return as
}

// mintJob builds a hub.JobWire carrying a signed storage_wipe envelope from the given signer.
func mintJob(t *testing.T, s testSigner, jobID, host, guest, keyID, paramsJSON string, issued, expires time.Time) hub.JobWire {
	t.Helper()
	blob, err := authz.CanonicalBlob("storage_wipe", host, guest, keyID, randNonce(), paramsJSON, issued, expires)
	if err != nil {
		t.Fatalf("CanonicalBlob: %v", err)
	}
	sig := mintArmor(s.pub.Marshal(), authz.Namespace, blob, s.sign)
	env := Envelope{OpBlobB64: base64.StdEncoding.EncodeToString(blob), SigArmored: string(sig)}
	envJSON, _ := json.Marshal(env)
	return hub.JobWire{JobID: jobID, BlobB64: base64.StdEncoding.EncodeToString(envJSON)}
}

func randNonce() string {
	var b [16]byte
	rand.Read(b[:])
	const hexd = "0123456789abcdef"
	out := make([]byte, 32)
	for i, x := range b {
		out[i*2] = hexd[x>>4]
		out[i*2+1] = hexd[x&0x0f]
	}
	return string(out)
}

func sha256Sum(b []byte) []byte {
	h := sha256.Sum256(b)
	return h[:]
}
