// Copyright 2022 schukai GmbH
// SPDX-License-Identifier: AGPL-3.0

package configuration

import (
	"github.com/stretchr/testify/assert"
	"os"
	"sync"
	"testing"
	"time"
)

func runTestFilesChange(fn string, data []testHostTimeout, t *testing.T) {

	for _, x := range data {

		h := x.host

		b := []byte("Host: \"" + h + "\"")
		err := os.WriteFile(fn, b, 0644)
		if err != nil {
			t.Error(err)
			return
		}

		time.Sleep(x.timeout)

	}

}

func createTestFileForWatch1() (string, error) {
	f, err := os.CreateTemp("", "watch_test")
	if err != nil {
		return "", err
	}

	_, _ = f.WriteString("Host: \"127.0.0.1\"")
	_ = f.Close()

	return f.Name(), nil
}

type testHostTimeout struct {
	host    string
	timeout time.Duration
}

func TestMultiChange(t *testing.T) {

	tmpFn, err := createTestFileForWatch1()
	if err != nil {
		t.Error(err)
		return
	}

	defer func() {
		err := os.Remove(tmpFn)
		if err != nil {
			t.Error(err)
		}
	}()

	config := struct {
		Host string `yaml:"Host"`
	}{
		Host: "localhost",
	}

	c := New(config)
	c.SetMnemonic("my-app")
	assert.Equal(t, c.Config().Host, "localhost")

	var mu sync.Mutex
	result := []string{}
	signal := make(chan string)

	var h ChangeHook
	h = &ChangeEventHandler{
		Callback: func(event ChangeEvent) {
			mu.Lock()
			result = append(result, event.Changelog[0].To.(string))
			mu.Unlock()
			signal <- event.Changelog[0].To.(string)
		},
	}

	var e ErrorHook
	e = &ErrorEventHandler{
		Callback: func(event ErrorEvent) {
			//for _, err := range c.Errors() {
			//	t.Error(err)
			//}
		},
	}

	c.AddFile(tmpFn, Yaml)
	c.Import()

	if c.HasErrors() {
		t.Error(c.Errors())
	}

	data := []testHostTimeout{

		// important to have a timeout > 500ms, because the decoupled file watcher will only trigger every 500ms

		{
			host:    "1.org",
			timeout: time.Millisecond * 550,
		},
		{
			host:    "2.org",
			timeout: time.Millisecond * 610,
		},
		{
			host:    "3.org",
			timeout: time.Millisecond * 602,
		},
		{
			host:    "4.org",
			timeout: time.Millisecond * 700,
		},
		{
			host:    "9.org",
			timeout: time.Millisecond * 700,
		},
	}

	c.OnChange(h).OnError(e).Watch()

	go runTestFilesChange(tmpFn, data, t)

	mu.Lock()
	length := len(result)
	mu.Unlock()

	for length < len(data) {
		select {
		case <-signal:
			mu.Lock()
			length = len(result)
			mu.Unlock()
			continue
		case <-time.After(time.Second * 20):
			t.Log(result)
			t.Fatalf("Timeout")
		}
	}

	assert.Equal(t, "9.org", c.Config().Host)

}

func TestWatch(t *testing.T) {

	f, err := os.CreateTemp("", "watch_test")
	if err != nil {
		t.Error(err)
		return
	}

	defer os.Remove(f.Name())

	config := struct {
		Host string `yaml:"Host"`
	}{
		Host: "localhost",
	}

	c := New(config)
	c.SetMnemonic("my-app")
	assert.Equal(t, c.Config().Host, "localhost")

	c.AddFile(f.Name(), Yaml)
	c.Import()

	signal := make(chan bool)

	var h ChangeHook
	h = &ChangeEventHandler{
		Callback: func(event ChangeEvent) {
			assert.Equal(t, event.Changelog[0].From, "localhost")
			assert.Equal(t, event.Changelog[0].To, "example.org")
			signal <- true
		},
	}

	c.OnChange(h)

	c.Watch()

	_, err = f.WriteString("Host: example.org")
	if err != nil {
		t.Error(err)
		return
	}

	select {
	case <-signal:
		assert.Equal(t, c.Config().Host, "example.org")
	case <-time.After(time.Second):
		t.Fatalf("Timeout")

	}

}

func TestSettingStopWatching(t *testing.T) {

	f, err := os.CreateTemp("", "watch_test")
	if err != nil {
		t.Error(err)
		return
	}

	defer func() {
		_ = os.Remove(f.Name())
		_ = f.Close()
	}()

	config := struct {
		Host string `yaml:"Host"`
	}{
		Host: "localhost",
	}

	c := New(config)
	c.SetMnemonic("my-app")
	assert.Equal(t, c.Config().Host, "localhost")

	c.AddFile(f.Name(), Yaml)
	c.Import().ResetErrors() // Import error is not relevant here

	c.Watch()
	c.StopWatching()

	if c.HasErrors() {
		t.Error(c.Errors())
	}

}

func TestSettingStopWatchingNotOnWatch(t *testing.T) {

	config := struct {
		Host string `yaml:"Host"`
	}{
		Host: "localhost",
	}

	c := New(config)
	c.StopWatching()

	if c.HasErrors() {
		t.Error("Expected to have no error")
	}

}

func TestSettingStopWatchingTwice(t *testing.T) {

	f, err := os.CreateTemp("", "watch_test")
	if err != nil {
		t.Error(err)
		return
	}

	defer func() {
		_ = os.Remove(f.Name())
		_ = f.Close()
	}()

	config := struct {
		Host string `yaml:"Host"`
	}{
		Host: "localhost",
	}

	_, _ = f.WriteString("Host: example.org")

	c := New(config)
	c.AddFile(f.Name(), Yaml)
	c.Import()
	c.Watch()

	c.StopWatching()
	c.StopWatching()

	e := c.Errors()
	if len(e) != 1 {
		t.Error("Expected to have an error")
	}

	errText := e[0].Error()
	assert.Equal(t, errText, "Watcher is not active")

}