// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.

//go:build go1.18

package exprs

import (
	"context"
	"strings"
	"testing"

	"github.com/apache/arrow-go/v18/arrow"
	"github.com/apache/arrow-go/v18/arrow/array"
	"github.com/apache/arrow-go/v18/arrow/compute"
	"github.com/apache/arrow-go/v18/arrow/memory"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

var (
	boringArrowSchema = arrow.NewSchema([]arrow.Field{
		{Name: "bool", Type: arrow.FixedWidthTypes.Boolean, Nullable: true},
		{Name: "i8", Type: arrow.PrimitiveTypes.Int8, Nullable: true},
		{Name: "i32", Type: arrow.PrimitiveTypes.Int32, Nullable: true},
		{Name: "i32_req", Type: arrow.PrimitiveTypes.Int32},
		{Name: "u32", Type: arrow.PrimitiveTypes.Uint32, Nullable: true},
		{Name: "i64", Type: arrow.PrimitiveTypes.Int64, Nullable: true},
		{Name: "f32", Type: arrow.PrimitiveTypes.Float32, Nullable: true},
		{Name: "f32_req", Type: arrow.PrimitiveTypes.Float32},
		{Name: "f64", Type: arrow.PrimitiveTypes.Float64, Nullable: true},
		{Name: "date32", Type: arrow.FixedWidthTypes.Date32, Nullable: true},
		{Name: "str", Type: arrow.BinaryTypes.String, Nullable: true},
		{Name: "bin", Type: arrow.BinaryTypes.Binary, Nullable: true},
	}, nil)
)

func TestMakeExecBatch(t *testing.T) {
	mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
	defer mem.AssertSize(t, 0)

	const numRows = 3
	var (
		ctx         = compute.WithAllocator(context.Background(), mem)
		i32, _, _   = array.FromJSON(mem, arrow.PrimitiveTypes.Int32, strings.NewReader(`[1, 2, 3]`))
		f32, _, _   = array.FromJSON(mem, arrow.PrimitiveTypes.Float32, strings.NewReader(`[1.5, 2.25, 3.125]`))
		empty, _, _ = array.RecordFromJSON(mem, boringArrowSchema, strings.NewReader(`[]`))
	)
	defer i32.Release()
	defer f32.Release()

	getField := func(n string) arrow.Field {
		f, _ := boringArrowSchema.FieldsByName(n)
		return f[0]
	}

	tests := []struct {
		name  string
		batch arrow.Record
	}{
		{"empty", empty},
		{"subset", array.NewRecord(arrow.NewSchema([]arrow.Field{getField("i32"), getField("f32")}, nil),
			[]arrow.Array{i32, f32}, numRows)},
		{"flipped subset", array.NewRecord(arrow.NewSchema([]arrow.Field{getField("f32"), getField("i32")}, nil),
			[]arrow.Array{f32, i32}, numRows)},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			defer tt.batch.Release()
			batch, err := makeExecBatch(ctx, boringArrowSchema, compute.NewDatumWithoutOwning(tt.batch))
			require.NoError(t, err)
			require.Equal(t, tt.batch.NumRows(), batch.Len)

			defer func() {
				for _, v := range batch.Values {
					v.Release()
				}
			}()

			for i, field := range boringArrowSchema.Fields() {
				typ := batch.Values[i].(compute.ArrayLikeDatum).Type()
				assert.Truef(t, arrow.TypeEqual(typ, field.Type),
					"expected: %s\ngot: %s", field.Type, typ)

				idxes := tt.batch.Schema().FieldIndices(field.Name)
				if batch.Values[i].Kind() == compute.KindScalar {
					assert.False(t, batch.Values[i].(*compute.ScalarDatum).Value.IsValid(),
						"null placeholder should be injected")
					assert.Len(t, idxes, 0, "should only happen when column isn't found")
				} else {
					col := tt.batch.Column(idxes[0])
					val := batch.Values[i].(*compute.ArrayDatum).MakeArray()
					defer val.Release()

					assert.Truef(t, array.Equal(col, val), "expected: %s\ngot: %s", col, val)
				}
			}
		})
	}
}
