package pkcs7

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/asn1"
	"errors"
	"math/big"
	"testing"
	"time"

	"github.com/certctl-io/certctl/internal/domain"
)

// SCEP RFC 8894 Phase 2.2: round-trip tests for ParseSignedData +
// SignerInfo.VerifySignature + auth-attr extractors.
//
// Each test materialises a real signing cert + signs auth-attrs over a
// known content, then re-parses and verifies. Catches drift between the
// signing-side encoding and the verification-side re-serialisation
// (RFC 5652 §5.4 SET OF Attribute quirk).

func TestSignerInfo_RoundTrip_RSAWithSHA256(t *testing.T) {
	signer, signerCert := genTestRSASigner(t)
	signedData := buildTestSignedData(t, signer, signerCert,
		domain.SCEPMessageTypePKCSReq, "txn-12345", []byte("0123456789abcdef"),
		[]byte("encapsulated content (typically EnvelopedData bytes)"))

	parsed, err := ParseSignedData(signedData)
	if err != nil {
		t.Fatalf("ParseSignedData: %v", err)
	}
	if len(parsed.SignerInfos) != 1 {
		t.Fatalf("len(SignerInfos) = %d, want 1", len(parsed.SignerInfos))
	}

	si := parsed.SignerInfos[0]
	if err := si.VerifySignature(); err != nil {
		t.Fatalf("VerifySignature: %v", err)
	}

	// Auth-attr extractors.
	mt, err := si.GetMessageType()
	if err != nil {
		t.Fatalf("GetMessageType: %v", err)
	}
	if mt != domain.SCEPMessageTypePKCSReq {
		t.Errorf("GetMessageType = %d, want %d", mt, domain.SCEPMessageTypePKCSReq)
	}
	tid, err := si.GetTransactionID()
	if err != nil {
		t.Fatalf("GetTransactionID: %v", err)
	}
	if tid != "txn-12345" {
		t.Errorf("GetTransactionID = %q, want %q", tid, "txn-12345")
	}
	nonce, err := si.GetSenderNonce()
	if err != nil {
		t.Fatalf("GetSenderNonce: %v", err)
	}
	if string(nonce) != "0123456789abcdef" {
		t.Errorf("GetSenderNonce = %q, want %q", nonce, "0123456789abcdef")
	}
}

func TestSignerInfo_RoundTrip_ECDSAWithSHA256(t *testing.T) {
	signer, signerCert := genTestECDSASigner(t)
	signedData := buildTestSignedData(t, signer, signerCert,
		domain.SCEPMessageTypeRenewalReq, "txn-ec-1", []byte("nonce-ec-aaaa-bbbb"),
		[]byte("encap content"))

	parsed, err := ParseSignedData(signedData)
	if err != nil {
		t.Fatalf("ParseSignedData: %v", err)
	}
	si := parsed.SignerInfos[0]
	if err := si.VerifySignature(); err != nil {
		t.Fatalf("VerifySignature (ECDSA): %v", err)
	}
	mt, err := si.GetMessageType()
	if err != nil {
		t.Fatalf("GetMessageType: %v", err)
	}
	if mt != domain.SCEPMessageTypeRenewalReq {
		t.Errorf("GetMessageType = %d, want RenewalReq (17)", mt)
	}
}

func TestSignerInfo_VerifySignature_TamperedAttrs_Refuses(t *testing.T) {
	signer, signerCert := genTestRSASigner(t)
	signedData := buildTestSignedData(t, signer, signerCert,
		domain.SCEPMessageTypePKCSReq, "txn-tamper", []byte("nonce-aaaa-bbbb"),
		[]byte("content"))

	parsed, err := ParseSignedData(signedData)
	if err != nil {
		t.Fatalf("ParseSignedData: %v", err)
	}
	si := parsed.SignerInfos[0]
	// Tamper with rawSignedAttrs by flipping the last byte. Re-verification
	// must reject — proves the signature is bound to the auth-attr bytes.
	si.rawSignedAttrs[len(si.rawSignedAttrs)-1] ^= 0x01
	if err := si.VerifySignature(); !errors.Is(err, ErrSignerInfoVerify) {
		t.Errorf("VerifySignature(tampered attrs) = %v, want ErrSignerInfoVerify", err)
	}
}

func TestParseSignedData_Empty_Refuses(t *testing.T) {
	if _, err := ParseSignedData(nil); err == nil {
		t.Error("ParseSignedData(nil) = nil, want error")
	}
	if _, err := ParseSignedData([]byte{}); err == nil {
		t.Error("ParseSignedData(empty) = nil, want error")
	}
}

func TestParseSignedData_Garbage_Refuses(t *testing.T) {
	garbage := []byte{0x30, 0x82, 0x05, 0x01, 0x02, 0x03}
	if _, err := ParseSignedData(garbage); err == nil {
		t.Error("ParseSignedData(garbage) = nil, want error")
	}
}

// --- helpers -------------------------------------------------------------

type testSigner interface {
	Sign(data []byte) ([]byte, error)
	DigestOID() asn1.ObjectIdentifier
	SignatureOID() asn1.ObjectIdentifier
}

type rsaTestSigner struct{ k *rsa.PrivateKey }

func (s *rsaTestSigner) Sign(data []byte) ([]byte, error) {
	h := sha256.Sum256(data)
	return rsa.SignPKCS1v15(rand.Reader, s.k, 0+5, h[:]) // 5 == crypto.SHA256 in crypto.Hash enum
}
func (s *rsaTestSigner) DigestOID() asn1.ObjectIdentifier    { return OIDSHA256 }
func (s *rsaTestSigner) SignatureOID() asn1.ObjectIdentifier { return OIDRSAWithSHA256 }

type ecdsaTestSigner struct{ k *ecdsa.PrivateKey }

func (s *ecdsaTestSigner) Sign(data []byte) ([]byte, error) {
	h := sha256.Sum256(data)
	return ecdsa.SignASN1(rand.Reader, s.k, h[:])
}
func (s *ecdsaTestSigner) DigestOID() asn1.ObjectIdentifier    { return OIDSHA256 }
func (s *ecdsaTestSigner) SignatureOID() asn1.ObjectIdentifier { return OIDECDSAWithSHA256 }

func genTestRSASigner(t *testing.T) (testSigner, *x509.Certificate) {
	t.Helper()
	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		t.Fatalf("rsa.GenerateKey: %v", err)
	}
	tmpl := &x509.Certificate{
		SerialNumber: big.NewInt(time.Now().UnixNano() ^ 0xDEAD),
		Subject:      pkix.Name{CommonName: "device-rsa"},
		Issuer:       pkix.Name{CommonName: "device-rsa"},
		NotBefore:    time.Now().Add(-time.Hour),
		NotAfter:     time.Now().Add(24 * time.Hour),
		KeyUsage:     x509.KeyUsageDigitalSignature,
	}
	der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
	if err != nil {
		t.Fatalf("CreateCertificate: %v", err)
	}
	cert, err := x509.ParseCertificate(der)
	if err != nil {
		t.Fatalf("ParseCertificate: %v", err)
	}
	return &rsaTestSigner{k: key}, cert
}

func genTestECDSASigner(t *testing.T) (testSigner, *x509.Certificate) {
	t.Helper()
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatalf("ecdsa.GenerateKey: %v", err)
	}
	tmpl := &x509.Certificate{
		SerialNumber: big.NewInt(time.Now().UnixNano() ^ 0xBEEF),
		Subject:      pkix.Name{CommonName: "device-ec"},
		Issuer:       pkix.Name{CommonName: "device-ec"},
		NotBefore:    time.Now().Add(-time.Hour),
		NotAfter:     time.Now().Add(24 * time.Hour),
		KeyUsage:     x509.KeyUsageDigitalSignature,
	}
	der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
	if err != nil {
		t.Fatalf("CreateCertificate: %v", err)
	}
	cert, err := x509.ParseCertificate(der)
	if err != nil {
		t.Fatalf("ParseCertificate: %v", err)
	}
	return &ecdsaTestSigner{k: key}, cert
}

// buildTestSignedData hand-constructs a CMS SignedData with one SignerInfo
// carrying SCEP authenticated attributes (messageType, transactionID,
// senderNonce, plus the standard CMS contentType + messageDigest).
//
// The signing pipeline mirrors what micromdm/scep + the ChromeOS SCEP
// client emit: the device hashes the encap content into messageDigest,
// the auth-attrs are SET-OF re-serialised, hashed, and signed.
//
// Implementation note: built directly with ASN1Wrap helpers rather than
// relying on asn1.Marshal of structs containing asn1.RawValue fields —
// asn1.Marshal of nested RawValues with mixed Class/Tag has been finicky
// and the helpers give us byte-level control that matches what's on the wire.
func buildTestSignedData(t *testing.T, signer testSigner, signerCert *x509.Certificate, messageType domain.SCEPMessageType, transactionID string, senderNonce, encapContent []byte) []byte {
	t.Helper()

	// 1. messageDigest auth-attr: SHA-256 of the encap content.
	contentDigest := sha256.Sum256(encapContent)

	// 2. Build each auth-attr as Attribute ::= SEQUENCE { OID, SET OF Value }
	//    using the helpers. Marshal each value individually then wrap.
	attrSetBody := buildSCEPAuthAttrs(t, contentDigest[:], messageType, transactionID, senderNonce)

	// 3. Compute the signature over SET OF Attribute.
	signedAttrsForSig := ASN1Wrap(0x31, attrSetBody)
	sig, err := signer.Sign(signedAttrsForSig)
	if err != nil {
		t.Fatalf("signer.Sign: %v", err)
	}

	// 4. Build the SignerInfo SEQUENCE byte-by-byte.
	versionBytes := []byte{0x02, 0x01, 0x01} // INTEGER 1
	// SID is IssuerAndSerialNumber: SEQUENCE { Issuer (RDN), SerialNumber INTEGER }
	serialDER, err := asn1.Marshal(signerCert.SerialNumber)
	if err != nil {
		t.Fatalf("marshal serial: %v", err)
	}
	sidBody := append([]byte{}, signerCert.RawIssuer...) // already in DER
	sidBody = append(sidBody, serialDER...)
	sidBytes := ASN1Wrap(0x30, sidBody)

	// DigestAlgorithm: AlgorithmIdentifier — encode via stdlib (small struct, no nested RawValue issues).
	digestAlgBytes := mustMarshal(t, pkix.AlgorithmIdentifier{Algorithm: signer.DigestOID(), Parameters: asn1.NullRawValue})

	// SignedAttrs as [0] IMPLICIT SET OF — tag 0xA0 wraps the SET body.
	signedAttrsImplicitBytes := ASN1Wrap(0xa0, attrSetBody)

	// SignatureAlgorithm.
	sigAlg := pkix.AlgorithmIdentifier{Algorithm: signer.SignatureOID()}
	if signer.SignatureOID().Equal(OIDRSAWithSHA256) {
		sigAlg.Parameters = asn1.NullRawValue
	}
	sigAlgBytes := mustMarshal(t, sigAlg)

	// Signature: OCTET STRING.
	sigOctetBytes := ASN1Wrap(0x04, sig)

	siBody := append([]byte{}, versionBytes...)
	siBody = append(siBody, sidBytes...)
	siBody = append(siBody, digestAlgBytes...)
	siBody = append(siBody, signedAttrsImplicitBytes...)
	siBody = append(siBody, sigAlgBytes...)
	siBody = append(siBody, sigOctetBytes...)
	siBytes := ASN1Wrap(0x30, siBody)

	// 5. Build encapContentInfo SEQUENCE { OID data, [0] EXPLICIT OCTET STRING }.
	octetBytes := ASN1Wrap(0x04, encapContent)         // OCTET STRING
	encapContentExplicit := ASN1Wrap(0xa0, octetBytes) // [0] EXPLICIT
	oidDataBytes := mustMarshal(t, OIDDataContent)
	encapBody := append([]byte{}, oidDataBytes...)
	encapBody = append(encapBody, encapContentExplicit...)
	encapBytes := ASN1Wrap(0x30, encapBody)

	// 6. certificates [0] IMPLICIT SET OF Certificate — body is one cert DER.
	certsBytes := ASN1Wrap(0xa0, signerCert.Raw)

	// 7. digestAlgorithms SET OF AlgorithmIdentifier (one entry).
	digestAlgsBytes := ASN1Wrap(0x31, digestAlgBytes)

	// 8. signerInfos SET OF SignerInfo (one entry).
	signerInfosBytes := ASN1Wrap(0x31, siBytes)

	// 9. Assemble SignedData SEQUENCE.
	sdBody := append([]byte{}, []byte{0x02, 0x01, 0x01}...) // version
	sdBody = append(sdBody, digestAlgsBytes...)
	sdBody = append(sdBody, encapBytes...)
	sdBody = append(sdBody, certsBytes...)
	sdBody = append(sdBody, signerInfosBytes...)
	sdSeq := ASN1Wrap(0x30, sdBody)

	// 10. Wrap as ContentInfo SEQUENCE { OID signedData, [0] EXPLICIT SignedData }.
	contentField := ASN1Wrap(0xa0, sdSeq)
	oidSignedDataDER := []byte{0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02}
	ciBody := append([]byte{}, oidSignedDataDER...)
	ciBody = append(ciBody, contentField...)
	return ASN1Wrap(0x30, ciBody)
}

// buildSCEPAuthAttrs builds the SET-OF body of SCEP auth-attrs (the bytes
// inside the [0] IMPLICIT SignedAttrs wrapper). Each Attribute is a SEQUENCE
// of (OID, SET OF Value); we build them with ASN1Wrap to avoid asn1.Marshal
// nuances with nested RawValues.
func buildSCEPAuthAttrs(t *testing.T, msgDigest []byte, messageType domain.SCEPMessageType, transactionID string, senderNonce []byte) []byte {
	t.Helper()
	var out []byte
	// contentType: SET OF OID = SET { OID data }
	out = append(out, attrSeq(t, OIDContentType, ASN1Wrap(0x06, []byte{0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01}))...)
	// messageDigest: SET OF OCTET STRING
	out = append(out, attrSeq(t, OIDMessageDigest, ASN1Wrap(0x04, msgDigest))...)
	// SCEP messageType: SET OF PrintableString (decimal ASCII)
	out = append(out, attrSeq(t, OIDSCEPMessageType, ASN1Wrap(0x13, []byte(intToAscii(int(messageType)))))...)
	// SCEP transactionID: SET OF PrintableString
	out = append(out, attrSeq(t, OIDSCEPTransactionID, ASN1Wrap(0x13, []byte(transactionID)))...)
	// SCEP senderNonce: SET OF OCTET STRING
	out = append(out, attrSeq(t, OIDSCEPSenderNonce, ASN1Wrap(0x04, senderNonce))...)
	return out
}

// attrSeq builds one Attribute SEQUENCE: SEQUENCE { OID, SET OF value }.
// The `value` arg is one already-encoded TLV (e.g. an OCTET STRING or
// PrintableString); attrSeq wraps it in a SET and prefixes the OID.
func attrSeq(t *testing.T, oid asn1.ObjectIdentifier, value []byte) []byte {
	t.Helper()
	oidBytes := mustMarshal(t, oid)
	setOfValue := ASN1Wrap(0x31, value)
	body := append([]byte{}, oidBytes...)
	body = append(body, setOfValue...)
	return ASN1Wrap(0x30, body)
}

func mustMarshal(t *testing.T, v interface{}) []byte {
	t.Helper()
	out, err := asn1.Marshal(v)
	if err != nil {
		t.Fatalf("marshal %T: %v", v, err)
	}
	return out
}

func intToAscii(i int) string {
	if i == 0 {
		return "0"
	}
	neg := i < 0
	if neg {
		i = -i
	}
	var b []byte
	for i > 0 {
		b = append([]byte{byte('0' + i%10)}, b...)
		i /= 10
	}
	if neg {
		b = append([]byte{'-'}, b...)
	}
	return string(b)
}
