package pgtype_test

import (
	"context"
	"fmt"
	"testing"

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

func TestCompositeCodecTranscode(t *testing.T) {
	skipCockroachDB(t, "Server does not support composite types (see https://github.com/cockroachdb/cockroach/issues/27792)")

	defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {

		_, err := conn.Exec(ctx, `drop type if exists ct_test;

create type ct_test as (
	a text,
  b int4
);`)
		require.NoError(t, err)
		defer conn.Exec(ctx, "drop type ct_test")

		dt, err := conn.LoadType(ctx, "ct_test")
		require.NoError(t, err)
		conn.TypeMap().RegisterType(dt)

		formats := []struct {
			name string
			code int16
		}{
			{name: "TextFormat", code: pgx.TextFormatCode},
			{name: "BinaryFormat", code: pgx.BinaryFormatCode},
		}

		for _, format := range formats {
			var a string
			var b int32

			err := conn.QueryRow(ctx, "select $1::ct_test", pgx.QueryResultFormats{format.code},
				pgtype.CompositeFields{"hi", int32(42)},
			).Scan(
				pgtype.CompositeFields{&a, &b},
			)
			require.NoErrorf(t, err, "%v", format.name)
			require.EqualValuesf(t, "hi", a, "%v", format.name)
			require.EqualValuesf(t, 42, b, "%v", format.name)
		}
	})
}

type point3d struct {
	X, Y, Z float64
}

func (p point3d) IsNull() bool {
	return false
}

func (p point3d) Index(i int) any {
	switch i {
	case 0:
		return p.X
	case 1:
		return p.Y
	case 2:
		return p.Z
	default:
		panic("invalid index")
	}
}

func (p *point3d) ScanNull() error {
	return fmt.Errorf("cannot scan NULL into point3d")
}

func (p *point3d) ScanIndex(i int) any {
	switch i {
	case 0:
		return &p.X
	case 1:
		return &p.Y
	case 2:
		return &p.Z
	default:
		panic("invalid index")
	}
}

func TestCompositeCodecTranscodeStruct(t *testing.T) {
	skipCockroachDB(t, "Server does not support composite types (see https://github.com/cockroachdb/cockroach/issues/27792)")

	defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {

		_, err := conn.Exec(ctx, `drop type if exists point3d;

create type point3d as (
	x float8,
	y float8,
	z float8
);`)
		require.NoError(t, err)
		defer conn.Exec(ctx, "drop type point3d")

		dt, err := conn.LoadType(ctx, "point3d")
		require.NoError(t, err)
		conn.TypeMap().RegisterType(dt)

		formats := []struct {
			name string
			code int16
		}{
			{name: "TextFormat", code: pgx.TextFormatCode},
			{name: "BinaryFormat", code: pgx.BinaryFormatCode},
		}

		for _, format := range formats {
			input := point3d{X: 1, Y: 2, Z: 3}
			var output point3d
			err := conn.QueryRow(ctx, "select $1::point3d", pgx.QueryResultFormats{format.code}, input).Scan(&output)
			require.NoErrorf(t, err, "%v", format.name)
			require.Equalf(t, input, output, "%v", format.name)
		}
	})
}

func TestCompositeCodecTranscodeStructWrapper(t *testing.T) {
	skipCockroachDB(t, "Server does not support composite types (see https://github.com/cockroachdb/cockroach/issues/27792)")

	defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {

		_, err := conn.Exec(ctx, `drop type if exists point3d;

create type point3d as (
	x float8,
	y float8,
	z float8
);`)
		require.NoError(t, err)
		defer conn.Exec(ctx, "drop type point3d")

		dt, err := conn.LoadType(ctx, "point3d")
		require.NoError(t, err)
		conn.TypeMap().RegisterType(dt)

		formats := []struct {
			name string
			code int16
		}{
			{name: "TextFormat", code: pgx.TextFormatCode},
			{name: "BinaryFormat", code: pgx.BinaryFormatCode},
		}

		type anotherPoint struct {
			X, Y, Z float64
		}

		for _, format := range formats {
			input := anotherPoint{X: 1, Y: 2, Z: 3}
			var output anotherPoint
			err := conn.QueryRow(ctx, "select $1::point3d", pgx.QueryResultFormats{format.code}, input).Scan(&output)
			require.NoErrorf(t, err, "%v", format.name)
			require.Equalf(t, input, output, "%v", format.name)
		}
	})
}

func TestCompositeCodecDecodeValue(t *testing.T) {
	skipCockroachDB(t, "Server does not support composite types (see https://github.com/cockroachdb/cockroach/issues/27792)")

	defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {

		_, err := conn.Exec(ctx, `drop type if exists point3d;

create type point3d as (
	x float8,
	y float8,
	z float8
);`)
		require.NoError(t, err)
		defer conn.Exec(ctx, "drop type point3d")

		dt, err := conn.LoadType(ctx, "point3d")
		require.NoError(t, err)
		conn.TypeMap().RegisterType(dt)

		formats := []struct {
			name string
			code int16
		}{
			{name: "TextFormat", code: pgx.TextFormatCode},
			{name: "BinaryFormat", code: pgx.BinaryFormatCode},
		}

		for _, format := range formats {
			rows, err := conn.Query(ctx, "select '(1,2,3)'::point3d", pgx.QueryResultFormats{format.code})
			require.NoErrorf(t, err, "%v", format.name)
			require.True(t, rows.Next())
			values, err := rows.Values()
			require.NoErrorf(t, err, "%v", format.name)
			require.Lenf(t, values, 1, "%v", format.name)
			require.Equalf(t, map[string]any{"x": 1.0, "y": 2.0, "z": 3.0}, values[0], "%v", format.name)
			require.False(t, rows.Next())
			require.NoErrorf(t, rows.Err(), "%v", format.name)
		}
	})
}

// Test for composite type from table instead of create type. Table types have system / hidden columns like tableoid,
// cmax, xmax, etc. These are not included when sending or receiving composite types.
//
// https://github.com/jackc/pgx/issues/1576
func TestCompositeCodecTranscodeStructWrapperForTable(t *testing.T) {
	skipCockroachDB(t, "Server does not support composite types (see https://github.com/cockroachdb/cockroach/issues/27792)")

	defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {

		_, err := conn.Exec(ctx, `drop table if exists point3d;

create table point3d (
	x float8,
	y float8,
	z float8
);`)
		require.NoError(t, err)
		defer conn.Exec(ctx, "drop table point3d")

		dt, err := conn.LoadType(ctx, "point3d")
		require.NoError(t, err)
		conn.TypeMap().RegisterType(dt)

		formats := []struct {
			name string
			code int16
		}{
			{name: "TextFormat", code: pgx.TextFormatCode},
			{name: "BinaryFormat", code: pgx.BinaryFormatCode},
		}

		type anotherPoint struct {
			X, Y, Z float64
		}

		for _, format := range formats {
			input := anotherPoint{X: 1, Y: 2, Z: 3}
			var output anotherPoint
			err := conn.QueryRow(ctx, "select $1::point3d", pgx.QueryResultFormats{format.code}, input).Scan(&output)
			require.NoErrorf(t, err, "%v", format.name)
			require.Equalf(t, input, output, "%v", format.name)
		}
	})
}