package pgtype_test

import (
	"bytes"
	"context"
	"testing"

	"github.com/jackc/pgx/v5/pgtype"
	"github.com/jackc/pgx/v5/pgtype/testutil"
	"github.com/stretchr/testify/require"
)

func isExpectedEqBytes(a interface{}) func(interface{}) bool {
	return func(v interface{}) bool {
		ab := a.([]byte)
		vb := v.([]byte)

		if (ab == nil) != (vb == nil) {
			return false
		}

		if ab == nil {
			return true
		}

		return bytes.Compare(ab, vb) == 0
	}
}

func TestByteaCodec(t *testing.T) {
	testutil.RunTranscodeTests(t, "bytea", []testutil.TranscodeTestCase{
		{[]byte{1, 2, 3}, new([]byte), isExpectedEqBytes([]byte{1, 2, 3})},
		{[]byte{}, new([]byte), isExpectedEqBytes([]byte{})},
		{[]byte(nil), new([]byte), isExpectedEqBytes([]byte(nil))},
		{nil, new([]byte), isExpectedEqBytes([]byte(nil))},
	})
}

func TestDriverBytesQueryRow(t *testing.T) {
	conn := testutil.MustConnectPgx(t)
	defer testutil.MustCloseContext(t, conn)

	ctx := context.Background()

	var buf []byte
	err := conn.QueryRow(ctx, `select $1::bytea`, []byte{1, 2}).Scan((*pgtype.DriverBytes)(&buf))
	require.EqualError(t, err, "cannot scan into *pgtype.DriverBytes from QueryRow")
}

func TestDriverBytes(t *testing.T) {
	conn := testutil.MustConnectPgx(t)
	defer testutil.MustCloseContext(t, conn)

	ctx := context.Background()

	argBuf := make([]byte, 128)
	for i := range argBuf {
		argBuf[i] = byte(i)
	}

	rows, err := conn.Query(ctx, `select $1::bytea from generate_series(1, 1000)`, argBuf)
	require.NoError(t, err)
	defer rows.Close()

	rowCount := 0
	resultBuf := argBuf
	detectedResultMutation := false
	for rows.Next() {
		rowCount++

		// At some point the buffer should be reused and change.
		if bytes.Compare(argBuf, resultBuf) != 0 {
			detectedResultMutation = true
		}

		err = rows.Scan((*pgtype.DriverBytes)(&resultBuf))
		require.NoError(t, err)

		require.Len(t, resultBuf, len(argBuf))
		require.Equal(t, resultBuf, argBuf)
		require.Equalf(t, cap(resultBuf), len(resultBuf), "cap(resultBuf) is larger than len(resultBuf)")
	}

	require.True(t, detectedResultMutation)

	err = rows.Err()
	require.NoError(t, err)
}

func TestPreallocBytes(t *testing.T) {
	conn := testutil.MustConnectPgx(t)
	defer testutil.MustCloseContext(t, conn)

	ctx := context.Background()

	origBuf := []byte{5, 6, 7, 8}
	buf := origBuf
	err := conn.QueryRow(ctx, `select $1::bytea`, []byte{1, 2}).Scan((*pgtype.PreallocBytes)(&buf))
	require.NoError(t, err)

	require.Len(t, buf, 2)
	require.Equal(t, 4, cap(buf))
	require.Equal(t, buf, []byte{1, 2})

	require.Equal(t, []byte{1, 2, 7, 8}, origBuf)

	err = conn.QueryRow(ctx, `select $1::bytea`, []byte{3, 4, 5, 6, 7}).Scan((*pgtype.PreallocBytes)(&buf))
	require.NoError(t, err)
	require.Len(t, buf, 5)
	require.Equal(t, 5, cap(buf))

	require.Equal(t, []byte{1, 2, 7, 8}, origBuf)
}

func TestUndecodedBytes(t *testing.T) {
	conn := testutil.MustConnectPgx(t)
	defer testutil.MustCloseContext(t, conn)

	ctx := context.Background()

	var buf []byte
	err := conn.QueryRow(ctx, `select 1`).Scan((*pgtype.UndecodedBytes)(&buf))
	require.NoError(t, err)

	require.Len(t, buf, 4)
	require.Equal(t, buf, []byte{0, 0, 0, 1})
}