package authz

import (
	"crypto/sha256"
	"crypto/sha512"
	"encoding/pem"
	"fmt"
	"hash"

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

// SSHSIG framing — ported verbatim-in-spirit from phase4-signing-findings.md §7.
// The only manual work is SSHSIG *framing*; all crypto and key-type dispatch is
// x/crypto/ssh's (pub.Verify dispatches on the key's own algorithm, which is what
// makes the verifier key-type-agnostic — ed25519 / sk-ssh-ed25519 / rsa / ecdsa).
// No hand-rolled crypto.

const sshsigMagic = "SSHSIG"

// sshsigBlob is the binary SSHSIG body (after the 6-byte magic). Field order is
// the SSH wire order — do not reorder.
type sshsigBlob struct {
	Version   uint32
	PublicKey string
	Namespace string
	Reserved  string
	HashAlgo  string
	Signature string
}

func hashByName(n string) (hash.Hash, error) {
	switch n {
	case "sha256":
		return sha256.New(), nil
	case "sha512":
		return sha512.New(), nil
	}
	return nil, fmt.Errorf("%w: unsupported SSHSIG hash %q", ErrMalformed, n)
}

// parseArmoredSSHSIG decodes the `-----BEGIN SSH SIGNATURE-----` armor into the
// SSHSIG body: pem.Decode → strip the literal 6-byte magic (not length-prefixed)
// → ssh.Unmarshal.
func parseArmoredSSHSIG(armored []byte) (*sshsigBlob, error) {
	block, _ := pem.Decode(armored)
	if block == nil || block.Type != "SSH SIGNATURE" {
		return nil, fmt.Errorf("%w: not an SSH SIGNATURE armor", ErrMalformed)
	}
	if len(block.Bytes) < len(sshsigMagic) || string(block.Bytes[:len(sshsigMagic)]) != sshsigMagic {
		return nil, fmt.Errorf("%w: missing SSHSIG magic", ErrMalformed)
	}
	var sb sshsigBlob
	if err := ssh.Unmarshal(block.Bytes[len(sshsigMagic):], &sb); err != nil {
		return nil, fmt.Errorf("%w: %v", ErrMalformed, err)
	}
	if sb.Version != 1 {
		return nil, fmt.Errorf("%w: bad SSHSIG version %d", ErrMalformed, sb.Version)
	}
	return &sb, nil
}

// signedData recomputes the bytes the signature actually covers, per the SSHSIG
// spec: "SSHSIG" || ssh.Marshal(namespace, reserved, hash_algorithm, H(message)),
// where H is the named hash. The message is the RAW received blob bytes — the
// verifier never canonicalizes (the canonical form is the signer's contract).
func signedData(sb *sshsigBlob, msg []byte) ([]byte, error) {
	h, err := hashByName(sb.HashAlgo)
	if err != nil {
		return nil, err
	}
	h.Write(msg)
	md := h.Sum(nil)
	body := ssh.Marshal(struct {
		Namespace string
		Reserved  string
		HashAlgo  string
		Hash      []byte
	}{sb.Namespace, sb.Reserved, sb.HashAlgo, md})
	return append([]byte(sshsigMagic), body...), nil
}
