package main

import (
	"flag"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"database/sql"
	"github.com/pingcap/schrodinger-test/util"
	"github.com/pingcap/schrodinger/pkg/test_client"
	"golang.org/x/net/context"
	"math/rand"
	"strings"
	"sync"
)

var defaultPushMetricsInterval = 15 * time.Second

var (
	dbName      = flag.String("db", "test", "database name")
	concurrency = flag.Int("concurrency", 200, "concurrency worker count")
	retryLimit  = flag.Int("retry-limit", 200, "retry count")
)

var (
	remark = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXVZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXVZlkjsanksqiszndqpijdslnnq"
)

func populateData(ctx context.Context, db *sql.DB, table string, count int) {
	// Insert batchSize values in one SQL.
	batchSize := 100
	args := make([]string, batchSize)
	for startIndex := 0; startIndex < count; startIndex = startIndex + batchSize {
		oneBatch := 0
		if count-startIndex >= batchSize {
			oneBatch = batchSize
		} else {
			oneBatch = count - startIndex
		}
		start := time.Now()
		for i := 0; i < oneBatch; i++ {
			args[i] = fmt.Sprintf("(%d, %f, \"%s\")", startIndex+i, 1000.0, remark[:rand.Intn(len(remark))])
		}

		query := fmt.Sprintf("INSERT IGNORE INTO %s (a, b, c) VALUES %s", table, strings.Join(args, ","))
		insertF := func() error {
			_, err := db.Exec(query)
			if util.IsErrDupEntry(err) {
				return nil
			}
			return err
		}
		err := util.RunWithRetry(ctx, *retryLimit, 5*time.Second, insertF)
		if err != nil {
			client.Fatalf("[%s]exec %s  err %s", table, query, err)
		}
		client.Infof("[%s] insert %d takes %s", table, oneBatch, table, time.Now().Sub(start))
	}
}

func createAndPopulateTables(ctx context.Context, db *sql.DB) error {

	util.MustExec(db, fmt.Sprintf("create table if not exists small_table (a int primary key, b double, c varchar(128))"))
	util.MustExec(db, fmt.Sprintf("create table if not exists big_table (a int primary key, b double, c varchar(128))"))

	var wg sync.WaitGroup

	wg.Add(2)

	go func() {
		defer wg.Done()
		populateData(ctx, db, "small_table", 100)
	}()
	go func() {
		defer wg.Done()
		populateData(ctx, db, "big_table", 10000000)
	}()

	wg.Wait()

	return nil
}

func main() {
	client.Info("[scan_out_of_bounds] start test")
	flag.Parse()

	ctx, cancel := context.WithCancel(context.Background())
	cli := client.New(ctx, client.ModeOnline)

	dbAddr := cli.TiDBService()
	dbDSN := fmt.Sprintf("root:@tcp(%s)/%s", dbAddr, *dbName)
	db, err := util.OpenDB(dbDSN, *concurrency)
	if err != nil {
		client.Fatalf("[scan_out_of_bounds] create db client error %v", err)
	}

	// Handle signals
	sc := make(chan os.Signal, 1)
	signal.Notify(sc,
		syscall.SIGHUP,
		syscall.SIGINT,
		syscall.SIGTERM,
		syscall.SIGQUIT)
	go func() {
		sig := <-sc
		client.Infof("[scan_out_of_bounds] Got signal [%s] to exist.", sig)
		cancel()
		os.Exit(0)
	}()

	// 1. create tables and populate data
	err = createAndPopulateTables(ctx, db)
	if err != nil {
		client.Fatalf("[scan_out_of_bounds] createAndPopulateTables error %v", err)
	}
	time.Sleep(10)

	// 2. drop big table
	util.MustExec(db, fmt.Sprintf("drop table big_table;"))

	// 3. invoke gc and wait till gc done
	util.MustExec(db, fmt.Sprintf("update mysql.tidb set VARIABLE_VALUE = '1m' where VARIABLE_NAME = 'tikv_gc_run_interval';"))
	time.Sleep(60)

	// 4. add index for small table, TiKV may panic here
	util.MustExec(db, fmt.Sprintf("alter table small_table add index b_1 (b);"))
}
