package scheduler

import (
	"encoding/json"
	"testing"
	"time"

	"github.com/docker/distribution/context"
	"github.com/docker/distribution/registry/storage/driver/inmemory"
)

func TestSchedule(t *testing.T) {
	timeUnit := time.Millisecond
	remainingRepos := map[string]bool{
		"testBlob1": true,
		"testBlob2": true,
		"ch00":      true,
	}

	s := New(context.Background(), inmemory.New(), "/ttl")
	deleteFunc := func(repoName string) error {
		if len(remainingRepos) == 0 {
			t.Fatalf("Incorrect expiry count")
		}
		_, ok := remainingRepos[repoName]
		if !ok {
			t.Fatalf("Trying to remove nonexistant repo: %s", repoName)
		}
		t.Log("removing", repoName)
		delete(remainingRepos, repoName)

		return nil
	}
	s.onBlobExpire = deleteFunc
	err := s.Start()
	if err != nil {
		t.Fatalf("Error starting ttlExpirationScheduler: %s", err)
	}

	s.add("testBlob1", 3*timeUnit, entryTypeBlob)
	s.add("testBlob2", 1*timeUnit, entryTypeBlob)

	func() {
		s.add("ch00", 1*timeUnit, entryTypeBlob)

	}()

	// Ensure all repos are deleted
	<-time.After(50 * timeUnit)
	if len(remainingRepos) != 0 {
		t.Fatalf("Repositories remaining: %#v", remainingRepos)
	}
}

func TestRestoreOld(t *testing.T) {
	remainingRepos := map[string]bool{
		"testBlob1": true,
		"oldRepo":   true,
	}

	deleteFunc := func(repoName string) error {
		if repoName == "oldRepo" && len(remainingRepos) == 3 {
			t.Errorf("oldRepo should be removed first")
		}
		_, ok := remainingRepos[repoName]
		if !ok {
			t.Fatalf("Trying to remove nonexistant repo: %s", repoName)
		}
		delete(remainingRepos, repoName)
		return nil
	}

	timeUnit := time.Millisecond
	serialized, err := json.Marshal(&map[string]schedulerEntry{
		"testBlob1": {
			Expiry:    time.Now().Add(1 * timeUnit),
			Key:       "testBlob1",
			EntryType: 0,
		},
		"oldRepo": {
			Expiry:    time.Now().Add(-3 * timeUnit), // TTL passed, should be removed first
			Key:       "oldRepo",
			EntryType: 0,
		},
	})
	if err != nil {
		t.Fatalf("Error serializing test data: %s", err.Error())
	}

	ctx := context.Background()
	pathToStatFile := "/ttl"
	fs := inmemory.New()
	err = fs.PutContent(ctx, pathToStatFile, serialized)
	if err != nil {
		t.Fatal("Unable to write serialized data to fs")
	}
	s := New(context.Background(), fs, "/ttl")
	s.onBlobExpire = deleteFunc
	err = s.Start()
	if err != nil {
		t.Fatalf("Error starting ttlExpirationScheduler: %s", err)
	}

	<-time.After(50 * timeUnit)
	if len(remainingRepos) != 0 {
		t.Fatalf("Repositories remaining: %#v", remainingRepos)
	}
}

func TestStopRestore(t *testing.T) {
	timeUnit := time.Millisecond
	remainingRepos := map[string]bool{
		"testBlob1": true,
		"testBlob2": true,
	}
	deleteFunc := func(repoName string) error {
		delete(remainingRepos, repoName)
		return nil
	}

	fs := inmemory.New()
	pathToStateFile := "/ttl"
	s := New(context.Background(), fs, pathToStateFile)
	s.onBlobExpire = deleteFunc

	err := s.Start()
	if err != nil {
		t.Fatalf(err.Error())
	}
	s.add("testBlob1", 300*timeUnit, entryTypeBlob)
	s.add("testBlob2", 100*timeUnit, entryTypeBlob)

	// Start and stop before all operations complete
	// state will be written to fs
	s.Stop()
	time.Sleep(10 * time.Millisecond)

	// v2 will restore state from fs
	s2 := New(context.Background(), fs, pathToStateFile)
	s2.onBlobExpire = deleteFunc
	err = s2.Start()
	if err != nil {
		t.Fatalf("Error starting v2: %s", err.Error())
	}

	<-time.After(500 * timeUnit)
	if len(remainingRepos) != 0 {
		t.Fatalf("Repositories remaining: %#v", remainingRepos)
	}

}

func TestDoubleStart(t *testing.T) {
	s := New(context.Background(), inmemory.New(), "/ttl")
	err := s.Start()
	if err != nil {
		t.Fatalf("Unable to start scheduler")
	}
	err = s.Start()
	if err == nil {
		t.Fatalf("Scheduler started twice without error")
	}
}