package main

import (
	"os"
	"testing"
)

func writeScheduleConfig(t *testing.T, contents string) {
	t.Helper()
	oldConfigDir := configDirOverride
	configDirOverride = t.TempDir()
	t.Cleanup(func() { configDirOverride = oldConfigDir })
	if err := os.WriteFile(configPath(), []byte(contents), 0600); err != nil {
		t.Fatal(err)
	}
}

func setupScheduleCallbackTest(t *testing.T) stateKey {
	t.Helper()
	writeScheduleConfig(t, "[[profiles]]\nname = 'scheduled'\n[profiles.schedule]\non_battery = true\nbackup = false\n")
	resetBusyState(t)
	return stateKey("scheduled")
}

func TestRunScheduleCallback(t *testing.T) {
	key := setupScheduleCallbackTest(t)
	var ran bool
	doneAfterRelease := false
	runScheduleCallback(key, func() {
		doneAfterRelease = !isProfileBusy(key)
	}, func(_ Config, _ int, prof Profile) {
		ran = prof.profileKey() == key
		if !isProfileBusy(key) {
			t.Error("profile was not busy during callback")
		}
	})
	if !ran {
		t.Fatal("schedule callback did not run")
	}
	if !doneAfterRelease {
		t.Fatal("completion callback ran before profile release")
	}
}

func TestRunScheduleCallbackGuards(t *testing.T) {
	t.Run("missing profile", func(t *testing.T) {
		setupScheduleCallbackTest(t)
		ran := false
		runScheduleCallback("missing", nil, func(Config, int, Profile) { ran = true })
		if ran {
			t.Fatal("callback ran for missing profile")
		}
	})

	t.Run("busy", func(t *testing.T) {
		key := setupScheduleCallbackTest(t)
		if !acquireProfile(key) {
			t.Fatal("could not mark profile busy")
		}
		defer releaseProfile(key)
		ran := false
		runScheduleCallback(key, nil, func(Config, int, Profile) { ran = true })
		if ran {
			t.Fatal("callback ran for busy profile")
		}
		if !isProfileBusy(key) {
			t.Fatal("callback released another operation's profile ownership")
		}
	})
}

func TestBuildDaemonScheduleValidation(t *testing.T) {
	tests := []struct {
		name    string
		config  string
		wantErr bool
	}{
		{
			name:   "missing environment",
			config: "[[profiles]]\nname = 'scheduled'\nenv_file = '/missing/profile.env'\n[profiles.schedule]\ncron = '0 * * * *'\n[profiles.backup]\npaths = ['/data']\n",
		},
		{
			name:    "unsupported backend",
			config:  "[[profiles]]\nname = 'scheduled'\nbackend = 'borg'\n[profiles.schedule]\ncron = '0 * * * *'\n[profiles.backup]\npaths = ['/data']\n",
			wantErr: true,
		},
		{
			name:    "missing backup paths",
			config:  "[[profiles]]\nname = 'scheduled'\n[profiles.schedule]\ncron = '0 * * * *'\n",
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			writeScheduleConfig(t, tt.config)
			sched, err := buildDaemonSchedule()
			if (err != nil) != tt.wantErr {
				t.Fatalf("buildDaemonSchedule() error = %v, wantErr %v", err, tt.wantErr)
			}
			if !tt.wantErr && len(sched.Entries()) != 1 {
				t.Fatalf("schedule entries = %d, want 1", len(sched.Entries()))
			}
		})
	}
}

func TestRunScheduleCallbackPanicCleanup(t *testing.T) {
	key := setupScheduleCallbackTest(t)
	done := false
	runScheduleCallback(key, func() { done = true }, func(Config, int, Profile) {
		panic("boom")
	})
	if isProfileBusy(key) {
		t.Fatal("profile remained busy after panic")
	}
	if !done {
		t.Fatal("completion callback did not run after panic")
	}
}
