package smime

import (
	"bytes"
	"crypto"
	"crypto/x509"
	"strings"
	"testing"
)

func testIdentity(t *testing.T, email string) (*x509.Certificate, crypto.PrivateKey) {
	t.Helper()
	certPEM, keyPEM, err := GenerateSelfSigned(email, DefaultValidity)
	if err != nil {
		t.Fatalf("GenerateSelfSigned: %v", err)
	}
	cert, err := ParseCertPEM(certPEM)
	if err != nil {
		t.Fatalf("ParseCertPEM: %v", err)
	}
	key, err := ParseKeyPEM(keyPEM)
	if err != nil {
		t.Fatalf("ParseKeyPEM: %v", err)
	}
	return cert, key
}

// testEntity uses CRLF line endings already, since Sign/Encrypt canonicalize the
// body to CRLF (MIME's wire form) before signing/encrypting — a round trip through
// either normalizes bare LF to CRLF, so tests compare against the canonical form.
func testEntity() Entity {
	return Entity{
		Headers: []string{"Content-Type: text/plain; charset=utf-8"},
		Body:    []byte("hello world\r\nsecond line\r\n"),
	}
}

func TestSignVerifyRoundTrip(t *testing.T) {
	cert, key := testIdentity(t, "alice@example.com")
	orig := testEntity()

	signed, err := Sign(orig, cert, key)
	if err != nil {
		t.Fatalf("Sign: %v", err)
	}
	if ct := HeaderValue(signed.Headers, "Content-Type"); !strings.HasPrefix(ct, "multipart/signed") {
		t.Fatalf("unexpected Content-Type: %q", ct)
	}

	inner, signer, err := VerifySigned(signed)
	if err != nil {
		t.Fatalf("VerifySigned: %v", err)
	}
	if signer == nil || signer.Subject.CommonName != "alice@example.com" {
		t.Fatalf("unexpected signer: %+v", signer)
	}
	if !bytes.Equal(inner.Body, orig.Body) {
		t.Fatalf("body mismatch: got %q want %q", inner.Body, orig.Body)
	}
	if HeaderValue(inner.Headers, "Content-Type") != HeaderValue(orig.Headers, "Content-Type") {
		t.Fatalf("header mismatch: got %v want %v", inner.Headers, orig.Headers)
	}
}

func TestVerifySignedDetectsTampering(t *testing.T) {
	cert, key := testIdentity(t, "alice@example.com")
	signed, err := Sign(testEntity(), cert, key)
	if err != nil {
		t.Fatalf("Sign: %v", err)
	}

	tampered := string(signed.Body)
	tampered = strings.Replace(tampered, "hello world", "hello WORLD", 1)
	signed.Body = []byte(tampered)

	inner, signer, err := VerifySigned(signed)
	if err == nil {
		t.Fatal("expected verification error for tampered content, got nil")
	}
	if signer != nil {
		t.Fatalf("expected nil signer on failed verification, got %+v", signer)
	}
	// The tampered body should still come back for display purposes even though
	// verification failed.
	if !bytes.Contains(inner.Body, []byte("hello WORLD")) {
		t.Fatalf("expected tampered body returned alongside the error, got %q", inner.Body)
	}
}

func TestVerifySignedWrongSignerCert(t *testing.T) {
	cert, key := testIdentity(t, "alice@example.com")
	other, _ := testIdentity(t, "mallory@example.com")

	signed, err := Sign(testEntity(), cert, key)
	if err != nil {
		t.Fatalf("Sign: %v", err)
	}
	_, signer, err := VerifySigned(signed)
	if err != nil {
		t.Fatalf("VerifySigned: %v", err)
	}
	if signer.Subject.CommonName == other.Subject.CommonName {
		t.Fatal("signer should not match an unrelated certificate")
	}
}

func TestEncryptDecryptRoundTrip(t *testing.T) {
	cert, key := testIdentity(t, "bob@example.com")
	orig := testEntity()

	encrypted, err := Encrypt(orig, []*x509.Certificate{cert})
	if err != nil {
		t.Fatalf("Encrypt: %v", err)
	}
	if ct := HeaderValue(encrypted.Headers, "Content-Type"); !strings.HasPrefix(ct, "application/pkcs7-mime") {
		t.Fatalf("unexpected Content-Type: %q", ct)
	}
	if bytes.Contains(encrypted.Body, orig.Body) {
		t.Fatal("encrypted body should not contain the plaintext")
	}

	decrypted, err := Decrypt(encrypted, cert, key)
	if err != nil {
		t.Fatalf("Decrypt: %v", err)
	}
	if !bytes.Equal(decrypted.Body, orig.Body) {
		t.Fatalf("body mismatch: got %q want %q", decrypted.Body, orig.Body)
	}
	if HeaderValue(decrypted.Headers, "Content-Type") != HeaderValue(orig.Headers, "Content-Type") {
		t.Fatalf("header mismatch: got %v want %v", decrypted.Headers, orig.Headers)
	}
}

func TestDecryptWrongKeyFails(t *testing.T) {
	cert, _ := testIdentity(t, "bob@example.com")
	otherCert, otherKey := testIdentity(t, "mallory@example.com")

	encrypted, err := Encrypt(testEntity(), []*x509.Certificate{cert})
	if err != nil {
		t.Fatalf("Encrypt: %v", err)
	}
	if _, err := Decrypt(encrypted, otherCert, otherKey); err == nil {
		t.Fatal("expected decryption with the wrong key to fail")
	}
}

func TestEncryptMultipleRecipientsBothCanDecrypt(t *testing.T) {
	senderCert, senderKey := testIdentity(t, "alice@example.com")
	recipCert, recipKey := testIdentity(t, "bob@example.com")
	orig := testEntity()

	encrypted, err := Encrypt(orig, []*x509.Certificate{senderCert, recipCert})
	if err != nil {
		t.Fatalf("Encrypt: %v", err)
	}

	senderCopy, err := Decrypt(encrypted, senderCert, senderKey)
	if err != nil {
		t.Fatalf("sender Decrypt: %v", err)
	}
	if !bytes.Equal(senderCopy.Body, orig.Body) {
		t.Fatal("sender's own copy did not decrypt to the original body")
	}

	recipCopy, err := Decrypt(encrypted, recipCert, recipKey)
	if err != nil {
		t.Fatalf("recipient Decrypt: %v", err)
	}
	if !bytes.Equal(recipCopy.Body, orig.Body) {
		t.Fatal("recipient's copy did not decrypt to the original body")
	}
}

// TestSignThenEncryptNestedRoundTrip covers the "sign and encrypt" compose option:
// the plaintext is signed, then the whole signed entity is encrypted (opaque
// nesting), matching how webui's compose handler applies both transforms together.
func TestSignThenEncryptNestedRoundTrip(t *testing.T) {
	senderCert, senderKey := testIdentity(t, "alice@example.com")
	recipCert, recipKey := testIdentity(t, "bob@example.com")
	orig := testEntity()

	signed, err := Sign(orig, senderCert, senderKey)
	if err != nil {
		t.Fatalf("Sign: %v", err)
	}
	encrypted, err := Encrypt(signed, []*x509.Certificate{recipCert})
	if err != nil {
		t.Fatalf("Encrypt: %v", err)
	}

	decrypted, err := Decrypt(encrypted, recipCert, recipKey)
	if err != nil {
		t.Fatalf("Decrypt: %v", err)
	}
	if !strings.HasPrefix(HeaderValue(decrypted.Headers, "Content-Type"), "multipart/signed") {
		t.Fatalf("expected the decrypted layer to still be multipart/signed, got %q", HeaderValue(decrypted.Headers, "Content-Type"))
	}

	inner, signer, err := VerifySigned(decrypted)
	if err != nil {
		t.Fatalf("VerifySigned: %v", err)
	}
	if signer.Subject.CommonName != "alice@example.com" {
		t.Fatalf("unexpected signer: %+v", signer)
	}
	if !bytes.Equal(inner.Body, orig.Body) {
		t.Fatalf("body mismatch after unwrapping both layers: got %q want %q", inner.Body, orig.Body)
	}
}

func TestImportPKCS12RejectsBadPassword(t *testing.T) {
	if _, _, err := ImportPKCS12([]byte("not a real pkcs12 file"), "whatever"); err == nil {
		t.Fatal("expected an error decoding garbage PKCS#12 data")
	}
}
