package main

import (
	"sync"
	"testing"
	"time"

	pingcaplog "github.com/pingcap/log"
	"github.com/pingcap/tidb/pkg/config"
	"github.com/stretchr/testify/require"
)

func TestAuditID(t *testing.T) {
	usedID := make(map[string]struct{})
	for i := 0; i < 10000; i++ {
		_, id := auditID()
		require.NotContains(t, usedID, id)
		usedID[id] = struct{}{}
	}
}

func TestAuditLogConfigUsesGlobalLogTimeout(t *testing.T) {
	tidbConf := config.NewConfig()
	tidbConf.Log.Timeout = 3
	tidbConf.Log.SlowQueryFile = "/tmp/tidb-slow.log"
	tidbConf.Instance.PluginAuditLogBufferSize = 4096
	tidbConf.Instance.PluginAuditLogFlushInterval = 5

	logCfg, auditLogFile := newAuditLogConfig(tidbConf)
	require.Equal(t, "/tmp/tidb-audit.log", auditLogFile)
	require.Equal(t, auditLogFile, logCfg.File.Filename)
	require.Equal(t, 3, logCfg.Timeout)
	require.True(t, logCfg.File.IsBuffered)
	require.Equal(t, 4096, logCfg.File.BufferSize)
	require.Equal(t, 5*time.Second, logCfg.File.BufferFlushInterval)
}

type blockingWriteSyncer struct {
	started chan struct{}
	release chan struct{}
	once    sync.Once
}

func newBlockingWriteSyncer() *blockingWriteSyncer {
	return &blockingWriteSyncer{
		started: make(chan struct{}),
		release: make(chan struct{}),
	}
}

func (w *blockingWriteSyncer) Write(p []byte) (int, error) {
	block := false
	w.once.Do(func() {
		block = true
		close(w.started)
	})
	if block {
		<-w.release
	}
	return len(p), nil
}

func (*blockingWriteSyncer) Sync() error {
	return nil
}

func TestAuditLogConfigPanicsWhenLogWriteTimeout(t *testing.T) {
	tidbConf := config.NewConfig()
	tidbConf.Log.Timeout = 1

	logCfg, _ := newAuditLogConfig(tidbConf)
	blockingWriter := newBlockingWriteSyncer()
	logger, _, err := pingcaplog.InitLoggerWithWriteSyncer(logCfg, blockingWriter, blockingWriter)
	require.NoError(t, err)

	done := make(chan struct{})
	go func() {
		defer close(done)
		logger.Info("first audit log")
	}()

	select {
	case <-blockingWriter.started:
	case <-time.After(3 * time.Second):
		require.FailNow(t, "timeout waiting for first audit log write")
	}

	require.PanicsWithValue(t, "Timeout of 1s when trying to write log", func() {
		logger.Info("second audit log")
	})

	close(blockingWriter.release)
	require.Eventually(t, func() bool {
		select {
		case <-done:
			return true
		default:
			return false
		}
	}, 3*time.Second, 10*time.Millisecond)
	require.NoError(t, logger.Sync())
}
