// Copyright 2022 Molecula Corp. (DBA FeatureBase).
// SPDX-License-Identifier: Apache-2.0
package etcd

import (
	"context"
	"os"
	"testing"
	"time"

	"github.com/featurebasedb/featurebase/v3/disco"
	"github.com/featurebasedb/featurebase/v3/logger"
	"github.com/featurebasedb/featurebase/v3/testhook"
	"github.com/pkg/errors"
	"go.etcd.io/etcd/server/v3/embed"
	"go.etcd.io/etcd/server/v3/etcdserver/api/v3client"

	"go.etcd.io/etcd/pkg/types"
)

const (
	initVal = "test"
	newVal  = "newValue"
)

func TestClusterKv(t *testing.T) {
	if !AllowCluster() {
		t.Skip("only testing clusters when clustering is allowed")
	}
	c := NewFakeCluster(t, 3, 2)
	err := c.Start()
	if err != nil {
		t.Fatalf("starting cluster: %v", err)
	}
	c.MustAwaitClusterState(disco.ClusterStateDown, 10*time.Second)
	err = c.BringUp()
	if err != nil {
		t.Fatalf("bringing up cluster: %v", err)
	}
	c.MustAwaitClusterState(disco.ClusterStateNormal, 10*time.Second)
	ctx := context.TODO()
	c.nodes[0].SetState(ctx, disco.NodeStateStarting)
	c.nodes[1].SetState(ctx, disco.NodeStateStarting)
	c.MustAwaitClusterState(disco.ClusterStateDown, 10*time.Second)
	c.nodes[0].SetState(ctx, disco.NodeStateStarted)
	// Two of three nodes are up, one is down, we have 2 replicas, so
	// we should be able to handle reads but not writes, so we're in
	// a Degraded state.
	c.MustAwaitClusterState(disco.ClusterStateDegraded, 10*time.Second)
	c.nodes[1].SetState(ctx, disco.NodeStateStarted)
	c.MustAwaitClusterState(disco.ClusterStateNormal, 10*time.Second)
	err = c.Stop()
	if err != nil {
		t.Fatalf("stopping cluster: %v", err)
	}
}

func TestLeasedKv(t *testing.T) {
	cfg := embed.NewConfig()

	clientURL := unixSocket(t)
	peerURL := unixSocket(t)
	cfg.LPUrls = types.MustNewURLs([]string{peerURL})
	cfg.APUrls = types.MustNewURLs([]string{peerURL})
	cfg.LCUrls = types.MustNewURLs([]string{clientURL})
	cfg.ACUrls = types.MustNewURLs([]string{clientURL})
	cfg.InitialCluster = cfg.Name + "=" + peerURL
	cfg.EnableGRPCGateway = false

	dir, err := testhook.TempDir(t, "leasedkv-*")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(dir)

	cfg.Dir = dir
	etcd, err := embed.StartEtcd(cfg)
	if err != nil {
		t.Fatal(err)
	}
	cli := v3client.New(etcd.Server)
	defer func() {
		etcd.Close()
		<-etcd.Server.StopNotify()
		cli.Close()
	}()
	wrapper := &Etcd{e: etcd, cli: cli, logger: logger.NewLogfLogger(t)}

	lkv := newLeasedKV(wrapper, context.TODO(), "/test", 1)

	ctx := context.Background()

	err = lkv.Start(initVal)
	if err != nil {
		t.Fatal(err)
	}

	v, err := lkv.Get(ctx)
	if err != nil {
		t.Fatal(err)
	}

	if v != initVal {
		t.Fatal("obtained value is not the same as expected. Obtained:", v, "Expected:", initVal)
	}

	err = lkv.Set(ctx, "otherValue")
	if err != nil {
		t.Fatal(err)
	}
	err = lkv.Set(ctx, "3")
	if err != nil {
		t.Fatal(err)
	}
	err = lkv.Set(ctx, newVal)
	if err != nil {
		t.Fatal(err)
	}

	v, err = lkv.Get(ctx)
	if err != nil {
		t.Fatal(err)
	}

	if v != newVal {
		t.Fatal("obtained value is not the same as expected. Obtained:", v, "Expected:", newVal)
	}

	time.Sleep(5 * time.Second)

	lkv.Stop()

	// we need to wait to force the lease expiration
	time.Sleep(5 * time.Second)

	_, err = lkv.Get(ctx)
	if err == nil || !errors.Is(err, disco.ErrNoResults) {
		t.Fatal("expected error:", disco.ErrNoResults, "obtained:", err)
	}
}
