package bun_test

import (
	"context"
	"database/sql"
	"os"
	"testing"

	_ "github.com/jackc/pgx/v5/stdlib"
	"github.com/uptrace/bun"
	"github.com/uptrace/bun/dialect/pgdialect"
)

// TestBunArrayColumns_NativeSlice verifies against a live PostgreSQL
// database (issue #13) that RelSpec's generated Bun array tags round-trip
// correctly through Bun's own pgdialect for both NOT NULL and nullable
// columns: NULL, '{}', and populated arrays must all scan and insert
// without a "bun: Scan(unsupported ...)" error.
//
// Requires the RELSPEC_TEST_PG_CONN environment variable, e.g.:
//
//	RELSPEC_TEST_PG_CONN="postgres://postgres:postgres@localhost:5432/relspec_test?sslmode=disable"
func TestBunArrayColumns_NativeSlice(t *testing.T) {
	connStr := os.Getenv("RELSPEC_TEST_PG_CONN")
	if connStr == "" {
		t.Skip("Skipping Bun array integration test: RELSPEC_TEST_PG_CONN environment variable not set")
	}

	sqldb, err := sql.Open("pgx", connStr)
	if err != nil {
		t.Fatalf("open connection: %v", err)
	}
	defer sqldb.Close()

	db := bun.NewDB(sqldb, pgdialect.New())
	ctx := context.Background()

	type notNullArrayRow struct {
		bun.BaseModel `bun:"table:relspec_test_arr_notnull"`
		ID            int64    `bun:"id,pk,autoincrement"`
		Tags          []string `bun:"tags,type:text[],notnull"`
	}

	type nullableArrayRow struct {
		bun.BaseModel `bun:"table:relspec_test_arr_nullable"`
		ID            int64     `bun:"id,pk,autoincrement"`
		Tags          *[]string `bun:"tags,type:text[]"`
	}

	if _, err := db.NewDropTable().Model((*notNullArrayRow)(nil)).IfExists().Exec(ctx); err != nil {
		t.Fatalf("drop table: %v", err)
	}
	if _, err := db.NewDropTable().Model((*nullableArrayRow)(nil)).IfExists().Exec(ctx); err != nil {
		t.Fatalf("drop table: %v", err)
	}
	if _, err := db.NewCreateTable().Model((*notNullArrayRow)(nil)).Exec(ctx); err != nil {
		t.Fatalf("create not-null table: %v", err)
	}
	if _, err := db.NewCreateTable().Model((*nullableArrayRow)(nil)).Exec(ctx); err != nil {
		t.Fatalf("create nullable table: %v", err)
	}
	t.Cleanup(func() {
		db.NewDropTable().Model((*notNullArrayRow)(nil)).IfExists().Exec(ctx)
		db.NewDropTable().Model((*nullableArrayRow)(nil)).IfExists().Exec(ctx)
	})

	t.Run("not null populated array", func(t *testing.T) {
		row := &notNullArrayRow{Tags: []string{"a", "b", "c"}}
		if _, err := db.NewInsert().Model(row).Exec(ctx); err != nil {
			t.Fatalf("insert: %v", err)
		}
		var out notNullArrayRow
		if err := db.NewSelect().Model(&out).Where("id = ?", row.ID).Scan(ctx); err != nil {
			t.Fatalf("select: %v", err)
		}
		if len(out.Tags) != 3 || out.Tags[0] != "a" || out.Tags[2] != "c" {
			t.Errorf("Tags = %v, want [a b c]", out.Tags)
		}
	})

	t.Run("not null empty array", func(t *testing.T) {
		row := &notNullArrayRow{Tags: []string{}}
		if _, err := db.NewInsert().Model(row).Exec(ctx); err != nil {
			t.Fatalf("insert: %v", err)
		}
		var out notNullArrayRow
		if err := db.NewSelect().Model(&out).Where("id = ?", row.ID).Scan(ctx); err != nil {
			t.Fatalf("select: %v", err)
		}
		if len(out.Tags) != 0 {
			t.Errorf("Tags = %v, want empty", out.Tags)
		}
	})

	t.Run("nullable NULL", func(t *testing.T) {
		row := &nullableArrayRow{Tags: nil}
		if _, err := db.NewInsert().Model(row).Exec(ctx); err != nil {
			t.Fatalf("insert: %v", err)
		}
		var out nullableArrayRow
		if err := db.NewSelect().Model(&out).Where("id = ?", row.ID).Scan(ctx); err != nil {
			t.Fatalf("select: %v", err)
		}
		if out.Tags != nil {
			t.Errorf("Tags = %v, want nil (SQL NULL)", *out.Tags)
		}
	})

	t.Run("nullable empty array", func(t *testing.T) {
		empty := []string{}
		row := &nullableArrayRow{Tags: &empty}
		if _, err := db.NewInsert().Model(row).Exec(ctx); err != nil {
			t.Fatalf("insert: %v", err)
		}
		var out nullableArrayRow
		if err := db.NewSelect().Model(&out).Where("id = ?", row.ID).Scan(ctx); err != nil {
			t.Fatalf("select: %v", err)
		}
		if out.Tags == nil {
			t.Fatalf("Tags = nil, want non-nil pointer to empty slice")
		}
		if len(*out.Tags) != 0 {
			t.Errorf("Tags = %v, want empty slice", *out.Tags)
		}
	})

	t.Run("nullable populated array", func(t *testing.T) {
		tags := []string{"x", "y"}
		row := &nullableArrayRow{Tags: &tags}
		if _, err := db.NewInsert().Model(row).Exec(ctx); err != nil {
			t.Fatalf("insert: %v", err)
		}
		var out nullableArrayRow
		if err := db.NewSelect().Model(&out).Where("id = ?", row.ID).Scan(ctx); err != nil {
			t.Fatalf("select: %v", err)
		}
		if out.Tags == nil || len(*out.Tags) != 2 || (*out.Tags)[0] != "x" {
			t.Errorf("Tags = %v, want [x y]", out.Tags)
		}
	})
}
