package main

import (
	"os"
	"os/exec"
	"testing"
	"time"
)

const busyTransitionGuard stateKey = "busy-transition-guard"

func resetProfileStateForTest(t *testing.T) {
	t.Helper()
	state.mu.Lock()
	oldActive := state.activeProfile
	oldProfiles := state.profileStates
	state.activeProfile = ""
	state.profileStates = make(map[stateKey]profileState)
	state.mu.Unlock()
	t.Cleanup(func() {
		state.mu.Lock()
		state.activeProfile = oldActive
		state.profileStates = oldProfiles
		state.mu.Unlock()
	})
}

func TestProfileStatePartialUpdates(t *testing.T) {
	resetProfileStateForTest(t)
	key := stateKey("alpha")
	resetProfileState([]stateKey{key})
	setProfileCronEntry(key, 42)
	want := profileState{errMsg: "config", repoErr: "repository", scheduleErr: "schedule", needsInit: true, entryID: 42}
	setProfileProbeState(key, want)

	got, ok := getProfileState(key)
	if !ok {
		t.Fatal("profile state missing")
	}
	if got != want {
		t.Fatalf("probe update = %+v, want %+v", got, want)
	}

	setProfileCronEntry(key, 73)
	want.entryID = 73
	got, _ = getProfileState(key)
	if got != want {
		t.Fatalf("cron update = %+v, want %+v", got, want)
	}
}

func TestProfileStateSurvivesReorder(t *testing.T) {
	resetProfileStateForTest(t)
	alpha := stateKey("alpha")
	beta := stateKey("beta")
	resetProfileState([]stateKey{alpha, beta})
	setProfileProbeState(alpha, profileState{errMsg: "alpha error"})
	setProfileProbeState(beta, profileState{repoErr: "beta error", needsInit: true})
	setProfileCronEntry(alpha, 11)
	setProfileCronEntry(beta, 22)
	setActiveProfileKey(beta)

	if active := resetProfileState([]stateKey{beta, alpha}); active != beta {
		t.Fatalf("active profile after reorder = %q, want %q", active, beta)
	}
	alphaState, _ := getProfileState(alpha)
	betaState, _ := getProfileState(beta)
	if alphaState.errMsg != "alpha error" || alphaState.entryID != 11 {
		t.Fatalf("alpha state moved during reorder: %+v", alphaState)
	}
	if betaState.repoErr != "beta error" || !betaState.needsInit || betaState.entryID != 22 {
		t.Fatalf("beta state moved during reorder: %+v", betaState)
	}
}

func TestActiveProfileFallsBackAfterRemoval(t *testing.T) {
	resetProfileStateForTest(t)
	alpha := stateKey("alpha")
	beta := stateKey("beta")
	resetProfileState([]stateKey{alpha, beta})
	setActiveProfileKey(beta)

	if active := resetProfileState([]stateKey{alpha}); active != alpha {
		t.Fatalf("active profile after removal = %q, want %q", active, alpha)
	}
	if _, ok := getProfileState(beta); ok {
		t.Fatal("removed profile state was retained")
	}
	if active := resetProfileState(nil); active != "" {
		t.Fatalf("active profile with no profiles = %q, want empty", active)
	}
}

func resetBusyState(t *testing.T) {
	t.Helper()

	state.mu.Lock()
	oldBusyProfiles := state.busyProfiles
	oldOnBusyChanged := state.onBusyChanged
	state.busyProfiles = map[stateKey]*exec.Cmd{busyTransitionGuard: nil}
	state.onBusyChanged = nil
	state.mu.Unlock()

	t.Cleanup(func() {
		state.mu.Lock()
		state.busyProfiles = oldBusyProfiles
		state.onBusyChanged = oldOnBusyChanged
		state.mu.Unlock()
	})
}

func TestCancelProfileInterruptsCommand(t *testing.T) {
	if os.Getenv("RESTRAY_STATE_TEST_HELPER") == "1" {
		time.Sleep(30 * time.Second)
		return
	}

	resetBusyState(t)
	key := stateKey("cancel")
	cmd := exec.Command(os.Args[0], "-test.run=^TestCancelProfileInterruptsCommand$")
	cmd.Env = append(os.Environ(), "RESTRAY_STATE_TEST_HELPER=1")
	if err := cmd.Start(); err != nil {
		t.Fatalf("start helper process: %v", err)
	}
	wait := make(chan error, 1)
	go func() { wait <- cmd.Wait() }()
	waited := false
	t.Cleanup(func() {
		if !waited {
			_ = cmd.Process.Kill()
			<-wait
		}
	})

	setProfileBusyCmd(key, cmd)
	cancelProfile(key)

	select {
	case err := <-wait:
		waited = true
		if err == nil {
			t.Fatal("helper process exited successfully; want interruption")
		}
	case <-time.After(5 * time.Second):
		t.Fatal("busy command did not stop after cancellation")
	}

	releaseProfile(key)
}
