// Copyright 2015 Daniel Theophanes.
// Use of this source code is governed by a zlib-style
// license that can be found in the LICENSE file.

// This needs to be run as root/admin hence the reason there is a build tag
// +build su

package service_test

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"testing"
	"time"

	"github.com/kardianos/service"
)

const runAsServiceArg = "RunThisAsService"

func TestMain(m *testing.M) {
	if len(os.Args) == 2 {
		os.Exit(m.Run())
	} else if len(os.Args) == 4 && os.Args[2] == runAsServiceArg {
		reportDir := os.Args[3]
		writeReport(reportDir, "call")
		runService()
		writeReport(reportDir, "finished")
		os.Exit(0)
	}

	log.Fatalf("Invalid arguments: %v", os.Args)
}

func TestInstallRunRestartStopRemove(t *testing.T) {
	p := &program{}
	reportDir := mustTempDir(t)
	defer os.RemoveAll(reportDir)

	s := mustNewRunAsService(t, p, reportDir)
	_ = s.Uninstall()

	if err := s.Install(); err != nil {
		t.Fatal("Install", err)
	}
	defer s.Uninstall()

	if err := s.Start(); err != nil {
		t.Fatal("Start", err)
	}
	defer s.Stop()
	checkReport(t, reportDir, "Start()", 1, 0)

	if err := s.Restart(); err != nil {
		t.Fatal("restart", err)
	}

	checkReport(t, reportDir, "Restart()", 2, 1)
	p.numStopped = 0
	if err := s.Stop(); err != nil {
		t.Fatal("stop", err)
	}
	checkReport(t, reportDir, "Stop()", 2, 2)

	if err := s.Uninstall(); err != nil {
		t.Fatal("uninstall", err)
	}
}

func runService() {
	p := &program{}
	sc := &service.Config{
		Name: "go_service_test",
	}
	s, err := service.New(p, sc)
	if err != nil {
		log.Fatal(err)
	}
	if err = s.Run(); err != nil {
		log.Fatal(err)
	}
}

func mustTempDir(t *testing.T) string {
	dir, err := ioutil.TempDir("", "servicetest")
	if err != nil {
		t.Fatal(err)
	}
	return dir
}

func writeReport(reportDir string, action string) {
	b := []byte("go_test_service_report")
	timeStamp := time.Now().UnixNano()
	err := ioutil.WriteFile(
		filepath.Join(reportDir, fmt.Sprintf("%d-%s", timeStamp, action)),
		b,
		0644,
	)
	if err != nil {
		log.Fatal(err)
	}
}

var matchActionRegexp = regexp.MustCompile("^(\\d+-)([a-z]*)$")

func getReport(
	t *testing.T,
	reportDir string,
) (numCalls int, numFinished int) {
	numCalls = 0
	numFinished = 0
	files, err := ioutil.ReadDir(reportDir)
	if err != nil {
		t.Fatalf("ReadDir(%s) err: %s", reportDir, err)
	}

	for _, file := range files {
		if matchActionRegexp.MatchString(file.Name()) {
			action := matchActionRegexp.ReplaceAllString(file.Name(), "$2")
			switch action {
			case "call":
				numCalls++
			case "finished":
				numFinished++
			default:
				t.Fatalf("getReport() found report with incorrect action: %s", action)
			}
		}
	}
	return
}

func checkReport(
	t *testing.T,
	reportDir string,
	msgPrefix string,
	wantNumCalled int,
	wantNumFinished int,
) {
	var numCalled int
	var numFinished int
	for i := 0; i < 25; i++ {
		numCalled, numFinished = getReport(t, reportDir)
		<-time.After(200 * time.Millisecond)
		if numCalled == wantNumCalled && numFinished == wantNumFinished {
			return
		}
	}
	if numCalled != wantNumCalled {
		t.Fatalf("%s - numCalled: %d, want %d",
			msgPrefix, numCalled, wantNumCalled)
	}
	if numFinished != wantNumFinished {
		t.Fatalf("%s - numFinished: %d, want %d",
			msgPrefix, numFinished, wantNumFinished)
	}
}

func mustNewRunAsService(
	t *testing.T,
	p *program,
	reportDir string,
) service.Service {
	sc := &service.Config{
		Name:      "go_service_test",
		Arguments: []string{"-test.v=true", runAsServiceArg, reportDir},
	}
	s, err := service.New(p, sc)
	if err != nil {
		t.Fatal(err)
	}
	return s
}
