package main

import (
	"context"
	"database/sql"
	"fmt"
	"strconv"
	"strings"

	"github.com/juju/errors"
	"github.com/pingcap/schrodinger-test/pkg/dbutil"
	"github.com/pingcap/schrodinger-test/util"
)

type checkResult struct {
	cfg        *checkConfig
	instanceID int64
	schemaID   int64
	tableID    int64
	schemaName string
	tableName  string
	originID   int64
	crc32      int64
	mappingID  int64
}

type checkConfig struct {
	calculateMore bool // calculate more data, including instanceID, tableID, mappingID etc.
	instanceID    int64
}

// instanceID  == 0 means non sharding
func getTableCrc32(db *sql.DB, cc *checkConfig, schemaName, tableName string, ignoreColumns []string) ([]*checkResult, error) {
	ctx := context.Background()
	ids, err := util.GetAllIDs(db, schemaName, tableName)
	if err != nil {
		return nil, errors.Trace(err)
	}

	var results []*checkResult
	for _, id := range ids {
		checkSum, err := checkLimitRange(ctx, db, schemaName, tableName, fmt.Sprintf("id=%d", id), ignoreColumns)
		if err != nil {
			return nil, errors.Trace(err)
		}
		res := &checkResult{
			cfg:        cc,
			originID:   id,
			crc32:      checkSum,
			schemaName: schemaName,
			tableName:  tableName,
		}
		if !cc.calculateMore {
			results = append(results, res)
			continue
		}

		res.instanceID = res.cfg.instanceID
		// hard code, we assume sharding table
		// schema = prefix_schemaID
		// table  = prefix_tableID
		schemaID, err := getID(schemaName)
		if err != nil {
			return nil, errors.Trace(err)
		}
		res.schemaID = schemaID

		tableID, err := getID(tableName)
		if err != nil {
			return nil, errors.Trace(err)
		}
		res.tableID = tableID

		// mappingID is used in non sharding case only
		mappingID, err := strconv.ParseInt(fmt.Sprintf("%d%d%d", res.schemaID, res.tableID, id), 10, 64)
		if err != nil {
			return nil, errors.Trace(err)
		}
		res.mappingID = mappingID

		results = append(results, res)
	}
	return results, nil
}

func computePartitionID(info *checkResult) int64 {
	return info.originID | (info.instanceID << 59) | (info.schemaID << 52) | (info.tableID << 44)
}

func checkLimitRange(ctx context.Context, db *sql.DB, schema, table, limitRange string, ignores []string) (int64, error) {
	tbInfo, err := dbutil.GetTableInfo(ctx, db, schema, table)
	if err != nil {
		return 0, errors.Trace(err)
	}
	checkSum, err := dbutil.GetCRC32ChecksumWithFilter(ctx, db, schema, table, tbInfo, limitRange, nil, util.SliceToMap(ignores))
	if err != nil {
		return 0, errors.Trace(err)
	}
	return checkSum, nil
}

// hardcode table name pattern
func getID(target string) (int64, error) {
	arr := strings.Split(target, "_")
	if len(arr) < 2 {
		return 0, errors.NotValidf("target %s", target)
	}

	i, err := strconv.ParseInt(arr[len(arr)-1], 10, 64)

	return i, errors.Trace(err)
}
