package servicebootstrap

import (
	"errors"
	"testing"
	"time"
)

type fakeManager struct {
	service     *fakeService
	createCalls int
	createdSpec Spec
}

func (m *fakeManager) OpenService(name string) (Service, error) {
	if m.service == nil {
		return nil, ErrNotInstalled
	}
	if name != m.service.name {
		return nil, errors.New("unexpected service name")
	}
	return m.service, nil
}

func (m *fakeManager) CreateService(spec Spec) (Service, error) {
	m.createCalls++
	m.createdSpec = spec
	m.service = &fakeService{name: spec.Name, states: []State{StateStopped}}
	return m.service, nil
}

type fakeService struct {
	name                  string
	states                []State
	stateIndex            int
	startCalls            int
	recoveryActionsCalls  int
	recoveryResetInterval uint32
	closed                bool
}

func (s *fakeService) Query() (State, error) {
	if len(s.states) == 0 {
		return StateStopped, nil
	}
	state := s.states[s.stateIndex]
	if s.stateIndex < len(s.states)-1 {
		s.stateIndex++
	}
	return state, nil
}

func (s *fakeService) Start() error {
	s.startCalls++
	if len(s.states) == 0 {
		s.states = []State{StateRunning}
		s.stateIndex = 0
		return nil
	}
	if s.states[len(s.states)-1] != StateRunning {
		s.states = append(s.states, StateRunning)
	}
	return nil
}

func (s *fakeService) SetRecoveryActions(_ []RecoveryAction, resetPeriod uint32) error {
	s.recoveryActionsCalls++
	s.recoveryResetInterval = resetPeriod
	return nil
}

func (s *fakeService) Close() error {
	s.closed = true
	return nil
}

func noSleep(time.Duration) {}

func TestEnsureRunningCreatesAndStartsMissingService(t *testing.T) {
	manager := &fakeManager{}

	err := EnsureRunning(manager, Spec{
		Name:         "miraged",
		Executable:   `C:\Program Files\Mirage\MirageGUI.exe`,
		DisplayName:  "Mirage",
		Description:  "Connects this computer to others on the Mirage network.",
		Dependencies: []string{"Dnscache"},
		RecoveryActions: []RecoveryAction{
			{Type: RecoveryActionRestart, Delay: time.Second},
		},
		RecoveryResetPeriod: 60,
	}, WaitConfig{Attempts: 3, Delay: time.Millisecond, Sleep: noSleep})
	if err != nil {
		t.Fatalf("EnsureRunning returned error: %v", err)
	}
	if manager.createCalls != 1 {
		t.Fatalf("createCalls = %d, want 1", manager.createCalls)
	}
	if manager.service == nil {
		t.Fatal("service not created")
	}
	if manager.service.startCalls != 1 {
		t.Fatalf("startCalls = %d, want 1", manager.service.startCalls)
	}
	if manager.service.recoveryActionsCalls != 1 {
		t.Fatalf("recoveryActionsCalls = %d, want 1", manager.service.recoveryActionsCalls)
	}
	if got, want := manager.createdSpec.Executable, `C:\Program Files\Mirage\MirageGUI.exe`; got != want {
		t.Fatalf("Executable = %q, want %q", got, want)
	}
}

func TestEnsureRunningStartsInstalledStoppedService(t *testing.T) {
	manager := &fakeManager{
		service: &fakeService{name: "miraged", states: []State{StateStopped}},
	}

	err := EnsureRunning(manager, Spec{Name: "miraged"}, WaitConfig{Attempts: 3, Delay: time.Millisecond, Sleep: noSleep})
	if err != nil {
		t.Fatalf("EnsureRunning returned error: %v", err)
	}
	if manager.createCalls != 0 {
		t.Fatalf("createCalls = %d, want 0", manager.createCalls)
	}
	if manager.service.startCalls != 1 {
		t.Fatalf("startCalls = %d, want 1", manager.service.startCalls)
	}
}

func TestEnsureRunningLeavesRunningServiceAlone(t *testing.T) {
	manager := &fakeManager{
		service: &fakeService{name: "miraged", states: []State{StateRunning}},
	}

	err := EnsureRunning(manager, Spec{Name: "miraged"}, WaitConfig{Attempts: 1, Delay: time.Millisecond, Sleep: noSleep})
	if err != nil {
		t.Fatalf("EnsureRunning returned error: %v", err)
	}
	if manager.service.startCalls != 0 {
		t.Fatalf("startCalls = %d, want 0", manager.service.startCalls)
	}
}

func TestEnsureRunningWaitsForPendingService(t *testing.T) {
	manager := &fakeManager{
		service: &fakeService{name: "miraged", states: []State{StateStartPending, StateStartPending, StateRunning}},
	}

	err := EnsureRunning(manager, Spec{Name: "miraged"}, WaitConfig{Attempts: 3, Delay: time.Millisecond, Sleep: noSleep})
	if err != nil {
		t.Fatalf("EnsureRunning returned error: %v", err)
	}
	if manager.service.startCalls != 0 {
		t.Fatalf("startCalls = %d, want 0", manager.service.startCalls)
	}
}
