package backup

import (
	"errors"
	"os"
	"path/filepath"
	"testing"
	"time"

	"zlh-agent/internal/state"
)

func TestRestoreCreatesCheckpointBeforeDestructiveRestore(t *testing.T) {
	cfg, serverRoot := setupBackupTest(t)
	mustWriteFile(t, filepath.Join(serverRoot, "world", "level.dat"), "target")

	target, err := Create(cfg)
	if err != nil {
		t.Fatalf("Create target backup: %v", err)
	}
	mustWriteFile(t, filepath.Join(serverRoot, "world", "level.dat"), "live")

	started := false
	startServerReady = func(*state.Config) error {
		started = true
		return nil
	}

	result, err := Restore(cfg, target.ID)
	if err != nil {
		t.Fatalf("Restore: %v", err)
	}
	if !started {
		t.Fatalf("expected restore to restart server through readiness-aware path")
	}
	if result.Backup.ID != target.ID {
		t.Fatalf("restored backup id = %q, want %q", result.Backup.ID, target.ID)
	}
	if result.Checkpoint.ID == "" || result.Checkpoint.ID == target.ID {
		t.Fatalf("checkpoint id = %q, target id = %q", result.Checkpoint.ID, target.ID)
	}
	if result.Checkpoint.Type != BackupTypeCheckpoint {
		t.Fatalf("checkpoint type = %q, want %q", result.Checkpoint.Type, BackupTypeCheckpoint)
	}
	if result.Checkpoint.Reason != BackupReasonPreRestore {
		t.Fatalf("checkpoint reason = %q, want %q", result.Checkpoint.Reason, BackupReasonPreRestore)
	}
	if got := mustReadFile(t, filepath.Join(serverRoot, "world", "level.dat")); got != "target" {
		t.Fatalf("restored file = %q, want target", got)
	}

	checkpointArchive := filepath.Join(rootDir, result.Checkpoint.Archive)
	checkpointRoot := t.TempDir()
	if err := restoreArchive(checkpointRoot, checkpointArchive, result.Checkpoint.Paths); err != nil {
		t.Fatalf("restore checkpoint archive for inspection: %v", err)
	}
	if got := mustReadFile(t, filepath.Join(checkpointRoot, "world", "level.dat")); got != "live" {
		t.Fatalf("checkpoint file = %q, want live", got)
	}
}

func TestRestoreAbortsIfCheckpointCreationFails(t *testing.T) {
	cfg, serverRoot := setupBackupTest(t)
	mustWriteFile(t, filepath.Join(serverRoot, "world", "level.dat"), "target")

	target, err := Create(cfg)
	if err != nil {
		t.Fatalf("Create target backup: %v", err)
	}
	mustWriteFile(t, filepath.Join(serverRoot, "world", "level.dat"), "live")

	checkpointErr := errors.New("save-off failed")
	getServerPID = func() (int, bool) { return 123, true }
	runSaveOff = func() error { return checkpointErr }

	stopped := false
	stopServerAndWait = func(time.Duration) error {
		stopped = true
		return nil
	}
	started := false
	startServerReady = func(*state.Config) error {
		started = true
		return nil
	}

	if _, err := Restore(cfg, target.ID); err == nil {
		t.Fatalf("Restore succeeded, want checkpoint failure")
	}
	if stopped {
		t.Fatalf("server stop was called after checkpoint failure")
	}
	if started {
		t.Fatalf("server start was called after checkpoint failure")
	}
	if got := mustReadFile(t, filepath.Join(serverRoot, "world", "level.dat")); got != "live" {
		t.Fatalf("live file changed after checkpoint failure: %q", got)
	}
}

func TestManualBackupIsManualAndCheckpointIsListable(t *testing.T) {
	cfg, serverRoot := setupBackupTest(t)
	mustWriteFile(t, filepath.Join(serverRoot, "world", "level.dat"), "target")

	manual, err := Create(cfg)
	if err != nil {
		t.Fatalf("Create manual backup: %v", err)
	}
	if manual.Type != BackupTypeManual {
		t.Fatalf("manual type = %q, want %q", manual.Type, BackupTypeManual)
	}
	if manual.Reason != "" {
		t.Fatalf("manual reason = %q, want empty", manual.Reason)
	}

	mustWriteFile(t, filepath.Join(serverRoot, "world", "level.dat"), "live")
	startServerReady = func(*state.Config) error { return nil }
	result, err := Restore(cfg, manual.ID)
	if err != nil {
		t.Fatalf("Restore: %v", err)
	}

	backups, err := List()
	if err != nil {
		t.Fatalf("List: %v", err)
	}
	seen := map[string]Manifest{}
	for _, manifest := range backups {
		seen[manifest.ID] = manifest
	}
	if seen[manual.ID].Type != BackupTypeManual {
		t.Fatalf("listed manual type = %q, want %q", seen[manual.ID].Type, BackupTypeManual)
	}
	checkpoint := seen[result.Checkpoint.ID]
	if checkpoint.Type != BackupTypeCheckpoint || checkpoint.Reason != BackupReasonPreRestore {
		t.Fatalf("listed checkpoint = type %q reason %q", checkpoint.Type, checkpoint.Reason)
	}
}

func TestRestoreArchiveRejectsUnsafeManifestPathBeforeDeleting(t *testing.T) {
	serverRoot := t.TempDir()
	archiveRoot := t.TempDir()
	mustWriteFile(t, filepath.Join(serverRoot, "world", "level.dat"), "live")
	mustWriteFile(t, filepath.Join(archiveRoot, "world", "level.dat"), "target")

	manifest := Manifest{
		ID:    "safe",
		Paths: []string{"world"},
	}
	archivePath := filepath.Join(t.TempDir(), "safe.tar.gz")
	if err := writeArchive(archiveRoot, archivePath, &manifest); err != nil {
		t.Fatalf("writeArchive: %v", err)
	}

	if err := restoreArchive(serverRoot, archivePath, []string{"../world"}); err == nil {
		t.Fatalf("restoreArchive succeeded with unsafe path")
	}
	if got := mustReadFile(t, filepath.Join(serverRoot, "world", "level.dat")); got != "live" {
		t.Fatalf("live file changed after unsafe path rejection: %q", got)
	}
}

func setupBackupTest(t *testing.T) (*state.Config, string) {
	t.Helper()

	oldRootDir := rootDir
	oldServerDir := serverDir
	oldGetServerPID := getServerPID
	oldRunSaveOff := runSaveOff
	oldRunSaveOn := runSaveOn
	oldStopServerAndWait := stopServerAndWait
	oldStartServerReady := startServerReady

	rootDir = t.TempDir()
	serverRoot := t.TempDir()
	serverDir = func(state.Config) string { return serverRoot }
	getServerPID = func() (int, bool) { return 0, false }
	runSaveOff = func() error { return nil }
	runSaveOn = func() error { return nil }
	stopServerAndWait = func(time.Duration) error { return nil }
	startServerReady = func(*state.Config) error { return nil }

	t.Cleanup(func() {
		rootDir = oldRootDir
		serverDir = oldServerDir
		getServerPID = oldGetServerPID
		runSaveOff = oldRunSaveOff
		runSaveOn = oldRunSaveOn
		stopServerAndWait = oldStopServerAndWait
		startServerReady = oldStartServerReady
	})

	return &state.Config{
		ContainerType: "game",
		Game:          "minecraft",
		Variant:       "vanilla",
		Version:       "1.21.4",
		VMID:          5001,
		World:         "world",
	}, serverRoot
}

func mustWriteFile(t *testing.T, path, data string) {
	t.Helper()
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		t.Fatalf("MkdirAll(%s): %v", filepath.Dir(path), err)
	}
	if err := os.WriteFile(path, []byte(data), 0o644); err != nil {
		t.Fatalf("WriteFile(%s): %v", path, err)
	}
}

func mustReadFile(t *testing.T, path string) string {
	t.Helper()
	data, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("ReadFile(%s): %v", path, err)
	}
	return string(data)
}
