// Copyright (C) The Arvados Authors. All rights reserved.
//
// SPDX-License-Identifier: AGPL-3.0

package dblock

import (
	"bytes"
	"context"
	"sync"
	"testing"
	"time"

	"git.arvados.org/arvados.git/lib/config"
	"git.arvados.org/arvados.git/sdk/go/arvados"
	"git.arvados.org/arvados.git/sdk/go/arvadostest"
	"git.arvados.org/arvados.git/sdk/go/ctxlog"
	"github.com/jmoiron/sqlx"
	"github.com/sirupsen/logrus"
	check "gopkg.in/check.v1"
)

func Test(t *testing.T) {
	check.TestingT(t)
}

var _ = check.Suite(&suite{})

type suite struct {
	cluster *arvados.Cluster
	db      *sqlx.DB
	getdb   func(context.Context) (*sqlx.DB, error)
}

var testLocker = &DBLocker{key: 999}

func (s *suite) SetUpSuite(c *check.C) {
	cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).Load()
	c.Assert(err, check.IsNil)
	s.cluster, err = cfg.GetCluster("")
	c.Assert(err, check.IsNil)
	s.db = arvadostest.DB(c, s.cluster)
	s.getdb = func(context.Context) (*sqlx.DB, error) { return s.db, nil }
}

func (s *suite) TestLock(c *check.C) {
	retryDelay = 10 * time.Millisecond

	var logbuf bytes.Buffer
	logger := ctxlog.New(&logbuf, "text", "debug")
	logger.Level = logrus.DebugLevel
	ctx := ctxlog.Context(context.Background(), logger)
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	testLocker.Lock(ctx, s.getdb)
	testLocker.Check()

	lock2 := make(chan bool)
	var wg sync.WaitGroup
	defer wg.Wait()
	wg.Add(1)
	go func() {
		defer wg.Done()
		testLocker2 := &DBLocker{key: 999}
		testLocker2.Lock(ctx, s.getdb)
		close(lock2)
		testLocker2.Check()
		testLocker2.Unlock()
	}()

	// Second lock should wait for first to Unlock
	select {
	case <-time.After(time.Second / 10):
		c.Check(logbuf.String(), check.Matches, `(?ms).*level=info.*DBClient="[^"]+:\d+".*ID=999.*`)
	case <-lock2:
		c.Log("double-lock")
		c.Fail()
	}

	testLocker.Check()
	testLocker.Unlock()

	// Now the second lock should succeed within retryDelay
	select {
	case <-time.After(retryDelay * 2):
		c.Log("timed out")
		c.Fail()
	case <-lock2:
	}
	c.Logf("%s", logbuf.String())
}