package cryptokit

import (
	"bytes"
	"crypto/ed25519"
	"crypto/rand"
	"crypto/rsa"
	"encoding/base64"
	"encoding/pem"
	"os"
	"os/exec"
	"testing"

	"filippo.io/age"
	"golang.org/x/crypto/ssh"
)

func TestEd25519SignVerifyAcceptsSeedEncoding(t *testing.T) {
	public, private, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	message := []byte("Aegis cryptographic test")
	signature, err := SignEd25519(message, base64.StdEncoding.EncodeToString(private.Seed()))
	if err != nil {
		t.Fatal(err)
	}
	if err := VerifyEd25519(message, signature, base64.StdEncoding.EncodeToString(public)); err != nil {
		t.Fatal(err)
	}
	if err := VerifyEd25519([]byte("tampered"), signature, base64.StdEncoding.EncodeToString(public)); err == nil {
		t.Fatal("tampered message verified")
	}
}

func TestEd25519PublicKeyAndFingerprint(t *testing.T) {
	public, private, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	derived, err := Ed25519PublicKey(base64.StdEncoding.EncodeToString(private.Seed()))
	if err != nil {
		t.Fatal(err)
	}
	if derived != base64.StdEncoding.EncodeToString(public) {
		t.Fatalf("derived public key = %q, want %q", derived, base64.StdEncoding.EncodeToString(public))
	}
	fingerprint, err := Ed25519PublicKeyFingerprint(derived)
	if err != nil {
		t.Fatal(err)
	}
	if len(fingerprint) != len("sha256:")+64 || fingerprint[:len("sha256:")] != "sha256:" {
		t.Fatalf("unexpected fingerprint %q", fingerprint)
	}
}

func TestAgeRoundTrip(t *testing.T) {
	identity, err := age.GenerateX25519Identity()
	if err != nil {
		t.Fatal(err)
	}
	ciphertext, err := EncryptAge([]byte("age test"), identity.Recipient().String())
	if err != nil {
		t.Fatal(err)
	}
	plaintext, err := DecryptAge(ciphertext, identity.String())
	if err != nil {
		t.Fatal(err)
	}
	if string(plaintext) != "age test" {
		t.Fatalf("unexpected plaintext %q", plaintext)
	}
}

func TestAgeSSHCompatibility(t *testing.T) {
	testKey := func(private any) {
		signer, err := ssh.NewSignerFromKey(private)
		if err != nil {
			t.Fatal(err)
		}
		privatePEM, err := ssh.MarshalPrivateKey(private, "")
		if err != nil {
			t.Fatal(err)
		}
		ciphertext, err := EncryptAge([]byte("age SSH test"), string(ssh.MarshalAuthorizedKey(signer.PublicKey())))
		if err != nil {
			t.Fatal(err)
		}
		plaintext, err := DecryptAge(ciphertext, string(pem.EncodeToMemory(privatePEM)))
		if err != nil {
			t.Fatal(err)
		}
		if string(plaintext) != "age SSH test" {
			t.Fatalf("unexpected plaintext %q", plaintext)
		}
	}
	_, edPrivate, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	testKey(edPrivate)
	rsaPrivate, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		t.Fatal(err)
	}
	testKey(rsaPrivate)
}

func TestOpenPGPRoundTripWithUserKeyMaterial(t *testing.T) {
	gpg, err := exec.LookPath("gpg")
	if err != nil {
		t.Skip("gpg is not installed")
	}
	home := t.TempDir()
	if err := os.Chmod(home, 0o700); err != nil {
		t.Fatal(err)
	}
	run := func(args []string, input []byte) ([]byte, error) {
		cmd := exec.Command(gpg, append([]string{"--batch", "--no-tty", "--no-options", "--homedir", home}, args...)...)
		cmd.Stdin = bytes.NewReader(input)
		return cmd.Output()
	}
	if _, err := run([]string{"--pinentry-mode", "loopback", "--passphrase", "", "--quick-generate-key", "Aegis Test <aegis@example.invalid>", "rsa2048", "sign", "1d"}, nil); err != nil {
		t.Skipf("gpg cannot create an ephemeral test key: %v", err)
	}
	if _, err := run([]string{"--pinentry-mode", "loopback", "--passphrase", "", "--quick-add-key", "aegis@example.invalid", "rsa2048", "encrypt", "1d"}, nil); err != nil {
		t.Skipf("gpg cannot create an ephemeral encryption subkey: %v", err)
	}
	publicKey, err := run([]string{"--armor", "--export", "aegis@example.invalid"}, nil)
	if err != nil {
		t.Fatal(err)
	}
	privateKey, err := run([]string{"--armor", "--export-secret-keys", "aegis@example.invalid"}, nil)
	if err != nil {
		t.Fatal(err)
	}
	message := []byte("OpenPGP compatibility test")
	signature, err := SignOpenPGPDetached(message, string(privateKey))
	if err != nil {
		t.Fatal(err)
	}
	if err := VerifyOpenPGPDetached(message, signature, string(publicKey)); err != nil {
		t.Fatal(err)
	}
	ciphertext, err := EncryptOpenPGP(message, string(publicKey), string(privateKey))
	if err != nil {
		t.Fatal(err)
	}
	plaintext, err := DecryptOpenPGP([]byte(ciphertext), string(privateKey))
	if err != nil {
		t.Fatal(err)
	}
	if string(plaintext) != string(message) {
		t.Fatalf("unexpected plaintext %q", plaintext)
	}
}
