package upgradeprep

import (
	"testing"
	"time"
)

type fakeManager struct {
	services map[string]*fakeService
}

func (m *fakeManager) OpenService(name string) (Service, error) {
	if len(m.services) == 0 {
		return nil, ErrNotInstalled
	}
	service, ok := m.services[name]
	if !ok {
		return nil, ErrNotInstalled
	}
	return service, nil
}

type fakeService struct {
	name       string
	states     []State
	stateIndex int
	stopCalls  int
	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) Stop() error {
	s.stopCalls++
	if len(s.states) == 0 || s.states[len(s.states)-1] != StateStopped {
		s.states = append(s.states, StateStopped)
	}
	return nil
}

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

type fakeLocker struct {
	files []string
	calls int
	err   error
}

func (l *fakeLocker) Terminate(paths []string) error {
	l.calls++
	l.files = append([]string(nil), paths...)
	return l.err
}

func noSleep(time.Duration) {}

func TestPrepareStopsRunningServiceAndTerminatesLockers(t *testing.T) {
	manager := &fakeManager{
		services: map[string]*fakeService{
			"miraged": {
				name:   "miraged",
				states: []State{StateRunning, StateStopPending, StateStopped},
			},
		},
	}
	locker := &fakeLocker{}

	err := Prepare(manager, locker, Config{
		ServiceName: "miraged",
		Files: []string{
			`C:\Program Files\Mirage\MirageGUI.exe`,
			`C:\Program Files\Mirage\mirage.exe`,
			`C:\Program Files\Mirage\Mirage.exe`,
			`C:\Program Files\Mirage\wintun.dll`,
		},
		Attempts: 4,
		Delay:    time.Millisecond,
		Sleep:    noSleep,
	})
	if err != nil {
		t.Fatalf("Prepare returned error: %v", err)
	}
	service := manager.services["miraged"]
	if got, want := service.stopCalls, 1; got != want {
		t.Fatalf("stopCalls = %d, want %d", got, want)
	}
	if got, want := locker.calls, 1; got != want {
		t.Fatalf("locker calls = %d, want %d", got, want)
	}
	if got, want := len(locker.files), 4; got != want {
		t.Fatalf("len(locker.files) = %d, want %d", got, want)
	}
	if !service.closed {
		t.Fatal("service should be closed")
	}
}

func TestPrepareContinuesWhenServiceNotInstalled(t *testing.T) {
	locker := &fakeLocker{}

	err := Prepare(&fakeManager{}, locker, Config{
		ServiceName: "miraged",
		Files:       []string{`C:\Program Files\Mirage\MirageGUI.exe`, `C:\Program Files\Mirage\mirage.exe`},
		Attempts:    1,
		Delay:       time.Millisecond,
		Sleep:       noSleep,
	})
	if err != nil {
		t.Fatalf("Prepare returned error: %v", err)
	}
	if got, want := locker.calls, 1; got != want {
		t.Fatalf("locker calls = %d, want %d", got, want)
	}
}

func TestPrepareFailsWhenServiceDoesNotStop(t *testing.T) {
	manager := &fakeManager{
		services: map[string]*fakeService{
			"miraged": {
				name:   "miraged",
				states: []State{StateRunning, StateStopPending, StateStopPending},
			},
		},
	}
	locker := &fakeLocker{}

	err := Prepare(manager, locker, Config{
		ServiceName: "miraged",
		Files:       []string{`C:\Program Files\Mirage\MirageGUI.exe`, `C:\Program Files\Mirage\mirage.exe`},
		Attempts:    2,
		Delay:       time.Millisecond,
		Sleep:       noSleep,
	})
	if err == nil {
		t.Fatal("Prepare unexpectedly succeeded")
	}
	if locker.calls != 0 {
		t.Fatalf("locker calls = %d, want 0", locker.calls)
	}
}

func TestPrepareStopsObsoleteServiceNameWhenPrimaryMissing(t *testing.T) {
	manager := &fakeManager{
		services: map[string]*fakeService{
			"miraged": {
				name:   "miraged",
				states: []State{StateRunning, StateStopPending, StateStopped},
			},
		},
	}
	locker := &fakeLocker{}

	err := Prepare(manager, locker, Config{
		ServiceNames: []string{"Mirage", "miraged"},
		Files: []string{
			`C:\Program Files\Mirage\MirageGUI.exe`,
			`C:\Program Files\Mirage\mirage.exe`,
			`C:\Program Files\Mirage\Mirage.exe`,
		},
		Attempts: 4,
		Delay:    time.Millisecond,
		Sleep:    noSleep,
	})
	if err != nil {
		t.Fatalf("Prepare returned error: %v", err)
	}
	service := manager.services["miraged"]
	if got, want := service.stopCalls, 1; got != want {
		t.Fatalf("stopCalls = %d, want %d", got, want)
	}
	if got, want := locker.calls, 1; got != want {
		t.Fatalf("locker calls = %d, want %d", got, want)
	}
}
