// Copyright 2021 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package keydecoder

import (
	"testing"

	"github.com/pingcap/tidb/infoschema"
	"github.com/pingcap/tidb/parser/model"
	_ "github.com/pingcap/tidb/planner/core"
	"github.com/pingcap/tidb/sessionctx/stmtctx"
	"github.com/pingcap/tidb/table"
	"github.com/pingcap/tidb/table/tables"
	"github.com/pingcap/tidb/testkit/testutil"
	"github.com/pingcap/tidb/types"
	"github.com/pingcap/tidb/util/codec"
	"github.com/stretchr/testify/assert"
	"go.opencensus.io/stats/view"
)

func TestDecodeKey(t *testing.T) {
	defer view.Stop()
	table.MockTableFromMeta = tables.MockTableFromMeta
	tableInfo1 := &model.TableInfo{
		ID:   1,
		Name: model.NewCIStr("table1"),
		Indices: []*model.IndexInfo{
			{ID: 1, Name: model.NewCIStr("index1"), State: model.StatePublic},
		},
	}
	tableInfo2 := &model.TableInfo{ID: 2, Name: model.NewCIStr("table2")}
	tableInfo3 := &model.TableInfo{
		ID:   3,
		Name: model.NewCIStr("table3"),
		Columns: []*model.ColumnInfo{
			{ID: 10, Name: model.NewCIStr("col"), State: model.StatePublic},
		},
		Indices: []*model.IndexInfo{
			{ID: 4, Name: model.NewCIStr("index4"), State: model.StatePublic},
		},
		Partition: &model.PartitionInfo{
			Type:   model.PartitionTypeRange,
			Expr:   "`col`",
			Enable: true,
			Definitions: []model.PartitionDefinition{
				{ID: 5, Name: model.NewCIStr("p0"), LessThan: []string{"10"}},
				{ID: 6, Name: model.NewCIStr("p1"), LessThan: []string{"MAXVALUE"}},
			},
		},
	}

	stubTableInfos := []*model.TableInfo{tableInfo1, tableInfo2, tableInfo3}
	stubInfoschema := infoschema.MockInfoSchema(stubTableInfos)

	decodedKey, err := DecodeKey([]byte{
		't',
		// table id = 1
		0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
		'_',
		'r',
		// int handle, value = 1
		0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
	}, stubInfoschema)
	assert.Nil(t, err)
	assert.Equal(t, int64(0), decodedKey.DbID)
	assert.Equal(t, "test", decodedKey.DbName)
	assert.Equal(t, int64(1), decodedKey.TableID)
	assert.Equal(t, "table1", decodedKey.TableName)
	assert.Equal(t, int64(0), decodedKey.PartitionID)
	assert.Equal(t, "", decodedKey.PartitionName)
	assert.Equal(t, IntHandle, decodedKey.HandleType)
	assert.False(t, decodedKey.IsPartitionHandle)
	assert.Equal(t, "1", decodedKey.HandleValue)
	// These are default values, ie. will be omitted when got marshaled into json
	assert.Equal(t, int64(0), decodedKey.IndexID)
	assert.Equal(t, "", decodedKey.IndexName)
	assert.Nil(t, decodedKey.IndexValues)

	ch := testutil.MustNewCommonHandle(t, 100, "abc")
	encodedCommonKey := ch.Encoded()
	key := []byte{
		't',
		// table id = 2
		0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
		'_',
		'r',
	}
	key = append(key, encodedCommonKey...)

	decodedKey, err = DecodeKey(key, stubInfoschema)
	assert.Nil(t, err)
	assert.Equal(t, int64(0), decodedKey.DbID)
	assert.Equal(t, "test", decodedKey.DbName)
	assert.Equal(t, int64(2), decodedKey.TableID)
	assert.Equal(t, "table2", decodedKey.TableName)
	assert.Equal(t, int64(0), decodedKey.PartitionID)
	assert.Equal(t, "", decodedKey.PartitionName)
	assert.Equal(t, CommonHandle, decodedKey.HandleType)
	assert.False(t, decodedKey.IsPartitionHandle)
	assert.Equal(t, "{100, abc}", decodedKey.HandleValue)
	// These are default values, ie. will be omitted when got marshaled into json
	assert.Equal(t, int64(0), decodedKey.IndexID)
	assert.Equal(t, "", decodedKey.IndexName)
	assert.Nil(t, decodedKey.IndexValues)

	values := types.MakeDatums("abc", 1)
	sc := &stmtctx.StatementContext{}
	encodedValue, err := codec.EncodeKey(sc, nil, values...)
	assert.Nil(t, err)
	key = []byte{
		't',
		// table id = 1
		0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
		'_',
		'i',
		// index id = 1
		0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
	}
	key = append(key, encodedValue...)

	decodedKey, err = DecodeKey(key, stubInfoschema)
	assert.Nil(t, err)
	assert.Equal(t, int64(0), decodedKey.DbID)
	assert.Equal(t, "test", decodedKey.DbName)
	assert.Equal(t, int64(1), decodedKey.TableID)
	assert.Equal(t, "table1", decodedKey.TableName)
	assert.Equal(t, int64(0), decodedKey.PartitionID)
	assert.Equal(t, "", decodedKey.PartitionName)
	assert.Equal(t, int64(1), decodedKey.IndexID)
	assert.Equal(t, "index1", decodedKey.IndexName)
	assert.Equal(t, []string{"abc", "1"}, decodedKey.IndexValues)
	// These are default values, ie. will be omitted when got marshaled into json
	assert.Equal(t, HandleType(""), decodedKey.HandleType)
	assert.Equal(t, "", decodedKey.HandleValue)
	assert.False(t, decodedKey.IsPartitionHandle)

	// Row key in a partitioned table.
	key = []byte("t\x80\x00\x00\x00\x00\x00\x00\x05_r\x80\x00\x00\x00\x00\x00\x00\x0a")
	decodedKey, err = DecodeKey(key, stubInfoschema)
	assert.Nil(t, err)
	assert.Equal(t, int64(0), decodedKey.DbID)
	assert.Equal(t, "test", decodedKey.DbName)
	assert.Equal(t, int64(3), decodedKey.TableID)
	assert.Equal(t, "table3", decodedKey.TableName)
	assert.Equal(t, int64(5), decodedKey.PartitionID)
	assert.Equal(t, "p0", decodedKey.PartitionName)
	assert.Equal(t, IntHandle, decodedKey.HandleType)
	assert.Equal(t, "10", decodedKey.HandleValue)
	// These are default values, ie. will be omitted when got marshaled into json
	assert.Equal(t, int64(0), decodedKey.IndexID)
	assert.Equal(t, "", decodedKey.IndexName)
	assert.Nil(t, decodedKey.IndexValues)
	assert.False(t, decodedKey.IsPartitionHandle)

	// Index key in a partitioned table.
	values = types.MakeDatums("abcde", 2)
	encodedValue, err = codec.EncodeKey(sc, nil, values...)
	assert.Nil(t, err)
	key = []byte("t\x80\x00\x00\x00\x00\x00\x00\x06_i\x80\x00\x00\x00\x00\x00\x00\x04")
	key = append(key, encodedValue...)

	decodedKey, err = DecodeKey(key, stubInfoschema)
	assert.Nil(t, err)
	assert.Equal(t, int64(0), decodedKey.DbID)
	assert.Equal(t, "test", decodedKey.DbName)
	assert.Equal(t, int64(3), decodedKey.TableID)
	assert.Equal(t, "table3", decodedKey.TableName)
	assert.Equal(t, int64(6), decodedKey.PartitionID)
	assert.Equal(t, "p1", decodedKey.PartitionName)
	assert.Equal(t, int64(4), decodedKey.IndexID)
	assert.Equal(t, "index4", decodedKey.IndexName)
	assert.Equal(t, []string{"abcde", "2"}, decodedKey.IndexValues)
	// These are default values, ie. will be omitted when got marshaled into json
	assert.Equal(t, HandleType(""), decodedKey.HandleType)
	assert.Equal(t, "", decodedKey.HandleValue)
	assert.False(t, decodedKey.IsPartitionHandle)

	// Totally invalid key
	key = []byte("this-is-a-totally-invalidkey")
	decodedKey, err = DecodeKey(key, stubInfoschema)
	assert.NotNil(t, err)
	// "partly" invalid key, i.e. neither record nor index
	key = []byte{
		't',
		// table id = 1
		0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
	}
	key = append(key, []byte("rest-part-is-invalid")...)
	decodedKey, err = DecodeKey(key, stubInfoschema)
	assert.NotNil(t, err)

	// Table cannot be found in infoschema
	// This is possible when the schema have changed since when the key is get.
	decodedKey, err = DecodeKey([]byte{
		't',
		// table id = 3
		0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
		'_',
		'r',
		// int handle, value = 1
		0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
	}, stubInfoschema)
	// We should get as much information as we can
	assert.Nil(t, err)
	assert.Equal(t, int64(4), decodedKey.TableID)
	assert.Equal(t, IntHandle, decodedKey.HandleType)
	assert.Equal(t, "1", decodedKey.HandleValue)

	// Rest information are all default value, ie. omitted when got marshaled into json
	assert.Equal(t, int64(0), decodedKey.DbID)
	assert.Equal(t, "", decodedKey.DbName)
	assert.Equal(t, "", decodedKey.TableName)
	assert.Equal(t, int64(0), decodedKey.PartitionID)
	assert.Equal(t, "", decodedKey.PartitionName)
	assert.Equal(t, int64(0), decodedKey.IndexID)
	assert.Equal(t, "", decodedKey.IndexName)
	assert.False(t, decodedKey.IsPartitionHandle)
	assert.Nil(t, decodedKey.IndexValues)
}
