package csbi

import (
	"context"
	"math/rand"
	"net"
	"os"
	"reflect"
	"testing"
	"time"

	"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/csbi"
	"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
	"github.com/google/uuid"
	log "github.com/sirupsen/logrus"
	"google.golang.org/grpc/peer"
)

func TestDeploymentStore_Set_Get_Delete(t *testing.T) {
	tests := []struct {
		name    string
		store   DeploymentStore
		wantErr bool
	}{
		{
			name:  "default",
			store: NewDeploymentStore(time.Second),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo := NewRepository("./models")
			ctx := peer.NewContext(context.Background(), &peer.Peer{Addr: &net.TCPAddr{}})
			d, err := Generate(ctx, ModelData, repo, southbound.Type_TYPE_CONTAINERISED)
			if err != nil {
				t.Error(err)
				return
			}

			tt.store.Set(d)
			got, err := tt.store.Get(d.ID)
			if (err != nil) != tt.wantErr {
				t.Errorf("DeploymentStore.Get() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, d) {
				t.Errorf("DeploymentStore.Get() = %v, want %v", got, d)
			}
			if err := tt.store.Delete(d.ID); (err != nil) != tt.wantErr {
				t.Errorf("DeploymentStore.Delete() error = %v, wantErr %v", err, tt.wantErr)
			}

			// wait for garbage collection to run
			time.Sleep(time.Second * 10)
		})
	}
}

func TestDeploymentStore_Shutdown(t *testing.T) {
	type args struct {
		ctx context.Context
	}
	tests := []struct {
		name  string
		store DeploymentStore
		args  args
	}{
		// TODO: Add test cases.
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.store.Shutdown(tt.args.ctx)
		})
	}
}

func Test_garbageCollector(t *testing.T) {
	type args struct {
		rounds int
		quota  float32
		short  bool
	}
	tests := []struct {
		name string
		args args
	}{
		{
			name: "default",
			args: args{
				rounds: 3,
				quota:  1,
				short:  true,
			},
		},
		{
			name: "70%",
			args: args{
				rounds: 10,
				quota:  0.7,
			},
		},
		{
			name: "50%",
			args: args{
				rounds: 10,
				quota:  0.5,
			},
		},
		{
			name: "10%",
			args: args{
				rounds: 10,
				quota:  0.11,
			},
		},
		{
			name: "none",
			args: args{
				rounds: 10,
				quota:  0,
			},
		},
		{
			name: "inconsistent",
			args: args{
				rounds: 10,
				quota:  0.333,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if testing.Short() && !tt.args.short {
				t.Skip("skip test in short mode")
			}
			activeDeployments := make(map[uuid.UUID]Deployment)
			decommissionedIDs := make([]uuid.UUID, 0)
			for i := 0; i < tt.args.rounds; i++ {
				repo := NewRepository("./models")
				ctx := peer.NewContext(context.Background(), &peer.Peer{Addr: &net.TCPAddr{}})
				d, err := Generate(ctx, ModelData, repo, southbound.Type_TYPE_CONTAINERISED)
				if err != nil {
					t.Error(err)
				}
				rval := rand.Float32()
				if tt.name == "inconsistent" {
					d.State = csbi.State_STATE_DECOMMISSIONED
					if err := os.RemoveAll(d.ID.String()); err != nil {
						log.Error(err)
					}
				} else {
					if rval < tt.args.quota {
						d.State = csbi.State_STATE_DECOMMISSIONED
						decommissionedIDs = append(decommissionedIDs, d.ID)
					}
				}
				activeDeployments[d.ID] = d
			}

			if err := garbageCollector(context.Background(), activeDeployments); err != nil {
				t.Error(err)
			}
			for _, id := range decommissionedIDs {
				_, ok := activeDeployments[id]
				if ok {
					t.Errorf("Deployment %v still exists", id)
				}
				if _, err := os.Stat(id.String()); !os.IsNotExist(err) {
					t.Errorf("Directory %v still exists", id)
				}
			}
		})
	}
}
