package main

import (
	"bytes"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"testing"
)

// TestEndToEndRealRsync drives the actual rsync binary through the actual rsh
// transport binary into the in-process SSH server, which runs the remote rsync
// via /bin/sh. It proves real rsync + the Go transport move files intact.
func TestEndToEndRealRsync(t *testing.T) {
	if testing.Short() {
		t.Skip("short mode")
	}
	rsyncBin, err := exec.LookPath("rsync")
	if err != nil {
		t.Skip("rsync not installed")
	}

	// Build the rsh binary so rsync can exec it as its remote shell.
	rshBin := filepath.Join(t.TempDir(), "rsh")
	if out, err := exec.Command("go", "build", "-o", rshBin, ".").CombinedOutput(); err != nil {
		t.Fatalf("build rsh: %v\n%s", err, out)
	}

	keyPath, pub := genClientKey(t)
	srv := newTestServer(t, pub, shellExec)
	kh := writeKnownHosts(t, "127.0.0.1", srv.port(), srv.hostKey.PublicKey())

	src := t.TempDir()
	dst := t.TempDir()
	mustWrite(t, filepath.Join(src, "a.txt"), "alpha")
	mustWrite(t, filepath.Join(src, "sub", "b.bin"), "\x00\x01\x02\x03beta")
	mustWrite(t, filepath.Join(src, "sub", "c.txt"), "gamma gamma gamma")

	cmd := exec.Command(rsyncBin, "-a", "-e", rshBin, src+"/", "u@127.0.0.1:"+dst+"/")
	cmd.Env = append(os.Environ(),
		"RSH_KEY="+keyPath,
		"RSH_KNOWN_HOSTS="+kh,
		"RSH_PORT="+strconv.Itoa(srv.port()),
	)
	if out, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("rsync: %v\n%s", err, out)
	}

	checkSame(t, src, dst)
}

func mustWrite(t *testing.T, path, content string) {
	t.Helper()
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
		t.Fatal(err)
	}
}

func checkSame(t *testing.T, src, dst string) {
	t.Helper()
	err := filepath.Walk(src, func(p string, info os.FileInfo, err error) error {
		if err != nil || info.IsDir() {
			return err
		}
		rel, _ := filepath.Rel(src, p)
		want, _ := os.ReadFile(p)
		got, err := os.ReadFile(filepath.Join(dst, rel))
		if err != nil {
			t.Errorf("missing on dest: %s (%v)", rel, err)
			return nil
		}
		if !bytes.Equal(got, want) {
			t.Errorf("content mismatch for %s", rel)
		}
		return nil
	})
	if err != nil {
		t.Fatal(err)
	}
}
