package keygen

import (
	"os"
	"path/filepath"
	"strings"
	"testing"

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

func TestGenerateED25519(t *testing.T) {
	r, err := Generate(AlgoED25519, "test@dial", nil)
	if err != nil {
		t.Fatal(err)
	}
	// Private key must parse back as an OpenSSH key.
	if _, err := ssh.ParseRawPrivateKey(r.PrivatePEM); err != nil {
		t.Fatalf("private key does not parse: %v", err)
	}
	// Public line must parse and carry the comment.
	pk, comment, _, _, err := ssh.ParseAuthorizedKey(r.PublicLine)
	if err != nil {
		t.Fatalf("public key does not parse: %v", err)
	}
	if pk.Type() != ssh.KeyAlgoED25519 {
		t.Errorf("expected ed25519, got %s", pk.Type())
	}
	if comment != "test@dial" {
		t.Errorf("comment = %q, want test@dial", comment)
	}
}

func TestGenerateWithPassphrase(t *testing.T) {
	r, err := Generate(AlgoED25519, "c", []byte("hunter2"))
	if err != nil {
		t.Fatal(err)
	}
	if _, err := ssh.ParseRawPrivateKey(r.PrivatePEM); err == nil {
		t.Error("encrypted key should not parse without passphrase")
	}
	if _, err := ssh.ParseRawPrivateKeyWithPassphrase(r.PrivatePEM, []byte("hunter2")); err != nil {
		t.Errorf("key should parse with passphrase: %v", err)
	}
}

func TestGenerateRSA(t *testing.T) {
	r, err := Generate(AlgoRSA, "c", nil)
	if err != nil {
		t.Fatal(err)
	}
	pk, _, _, _, err := ssh.ParseAuthorizedKey(r.PublicLine)
	if err != nil {
		t.Fatal(err)
	}
	if pk.Type() != ssh.KeyAlgoRSA {
		t.Errorf("expected rsa, got %s", pk.Type())
	}
}

func TestWriteKeyPairPermsAndNoOverwrite(t *testing.T) {
	dir := t.TempDir()
	r, _ := Generate(AlgoED25519, "c", nil)
	priv, pub, err := WriteKeyPair(dir, "mykey", r)
	if err != nil {
		t.Fatal(err)
	}
	fi, _ := os.Stat(priv)
	if fi.Mode().Perm() != 0o600 {
		t.Errorf("private key perms = %o, want 600", fi.Mode().Perm())
	}
	pfi, _ := os.Stat(pub)
	if pfi.Mode().Perm() != 0o644 {
		t.Errorf("public key perms = %o, want 644", pfi.Mode().Perm())
	}
	// Second write must refuse to overwrite.
	if _, _, err := WriteKeyPair(dir, "mykey", r); err == nil {
		t.Error("expected refusal to overwrite existing key")
	}
}

func TestAppendHostEntryBackupAndDup(t *testing.T) {
	dir := t.TempDir()
	cfg := filepath.Join(dir, "config")
	if err := os.WriteFile(cfg, []byte("Host existing\n    HostName e\n"), 0o600); err != nil {
		t.Fatal(err)
	}

	backup, err := AppendHostEntry(cfg, HostEntry{
		Alias: "newhost", HostName: "10.0.0.1", User: "ben", Port: "22",
		IdentityFile: "~/.ssh/mykey", Tags: []string{"prod"},
	})
	if err != nil {
		t.Fatal(err)
	}
	if backup == "" {
		t.Error("expected a backup path")
	}
	if _, err := os.Stat(backup); err != nil {
		t.Errorf("backup not written: %v", err)
	}
	b, _ := os.ReadFile(cfg)
	got := string(b)
	for _, want := range []string{"# tag: prod", "Host newhost", "HostName 10.0.0.1", "IdentitiesOnly yes"} {
		if !strings.Contains(got, want) {
			t.Errorf("config missing %q\n%s", want, got)
		}
	}
	// Port 22 is the default and should be omitted.
	if strings.Contains(got, "Port 22") {
		t.Error("default port 22 should not be written")
	}

	// Appending the same alias must be refused.
	if _, err := AppendHostEntry(cfg, HostEntry{Alias: "newhost", HostName: "x"}); err == nil {
		t.Error("expected duplicate-alias refusal")
	}
}

func TestValidateRejectsInjection(t *testing.T) {
	dir := t.TempDir()
	cfg := filepath.Join(dir, "config")
	if _, err := AppendHostEntry(cfg, HostEntry{Alias: "a b"}); err == nil {
		t.Error("alias with space should be rejected")
	}
	if _, err := AppendHostEntry(cfg, HostEntry{Alias: "ok", HostName: "x\n    ProxyCommand evil"}); err == nil {
		t.Error("newline injection should be rejected")
	}
}
