package scheduler

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

	"github.com/dtoro/oikos/internal/adapters/postgres/sqlcgen"
)

func backupCheckDef(t *testing.T, config string) sqlcgen.ListEnabledCheckDefsRow {
	t.Helper()
	return sqlcgen.ListEnabledCheckDefsRow{
		Kind:     "backup-freshness",
		Config:   []byte(config),
		TimeoutS: 15,
	}
}

// A misconfigured check must say so rather than quietly reporting healthy —
// "no path configured" and "backup ran fine" must never look the same.
func TestBackupFreshnessRejectsIncompleteConfig(t *testing.T) {
	for _, c := range []struct{ desc, config string }{
		{"no path", `{"host":"localhost"}`},
		{"no host", `{"path":"/tmp"}`},
		{"empty", `{}`},
	} {
		got := checkBackupFreshness(context.Background(), backupCheckDef(t, c.config))
		if got.health != "unknown" || got.signalKind != "backup-misconfigured" {
			t.Errorf("%s: got health=%q kind=%q, want unknown/backup-misconfigured",
				c.desc, got.health, got.signalKind)
		}
	}
}

// Live probe against a real SSH endpoint. Guarded by OIKOS_SSH_TEST_HOST:
//
//	OIKOS_SSH_TEST_HOST=localhost OIKOS_SSH_USER=$USER \
//	OIKOS_SSH_KEY_PATH=~/.ssh/id_ed25519 go test ./internal/scheduler/ -run TestBackupFreshnessLive
//
// The probe shell has to work on both GNU and BSD find — the first real target
// is the pre-deploy pg_dump on the macOS mac-mini, so a GNU-only construct
// would fail exactly where it matters.
func TestBackupFreshnessLiveDistinguishesTheFourStates(t *testing.T) {
	host := os.Getenv("OIKOS_SSH_TEST_HOST")
	if host == "" {
		t.Skip("OIKOS_SSH_TEST_HOST not set — skipping live backup probe")
	}
	sshKeyPath = os.Getenv("OIKOS_SSH_KEY_PATH")
	sshUser = os.Getenv("OIKOS_SSH_USER")

	dir := t.TempDir()
	fresh := filepath.Join(dir, "fresh")
	stale := filepath.Join(dir, "stale")
	empty := filepath.Join(dir, "empty")
	for _, d := range []string{fresh, stale, empty} {
		if err := os.Mkdir(d, 0o755); err != nil {
			t.Fatal(err)
		}
	}
	if err := os.WriteFile(filepath.Join(fresh, "dump.sql"), []byte("x"), 0o644); err != nil {
		t.Fatal(err)
	}
	oldFile := filepath.Join(stale, "dump.sql")
	if err := os.WriteFile(oldFile, []byte("x"), 0o644); err != nil {
		t.Fatal(err)
	}
	old := time.Now().Add(-72 * time.Hour)
	if err := os.Chtimes(oldFile, old, old); err != nil {
		t.Fatal(err)
	}

	cases := []struct {
		desc, path, wantHealth, wantKind string
	}{
		{"recent artifact", fresh, "healthy", ""},
		{"artifact older than max_age", stale, "degraded", "backup-stale"},
		{"directory exists but is empty", empty, "down", "backup-missing"},
		{"directory does not exist", filepath.Join(dir, "nope"), "down", "backup-missing"},
	}

	for _, c := range cases {
		cfg := `{"host":"` + host + `","path":"` + c.path + `","max_age_s":86400}`
		got := checkBackupFreshness(context.Background(), backupCheckDef(t, cfg))
		if got.health != c.wantHealth || got.signalKind != c.wantKind {
			t.Errorf("%s: got health=%q kind=%q evidence=%q, want %q/%q",
				c.desc, got.health, got.signalKind, got.evidence, c.wantHealth, c.wantKind)
		}
	}
}

// A path with a quote in it must not break out of the remote sh command.
func TestShellSingleQuoteEscapes(t *testing.T) {
	got := shellSingleQuote(`/tmp/it's; rm -rf /`)
	want := `'/tmp/it'\''s; rm -rf /'`
	if got != want {
		t.Errorf("shellSingleQuote = %s, want %s", got, want)
	}
}
