package postgres_test

import (
	"context"
	"embed"
	"io/fs"
	"testing"
	"time"

	"galaxy/postgres"

	testcontainers "github.com/testcontainers/testcontainers-go"
	tcpostgres "github.com/testcontainers/testcontainers-go/modules/postgres"
	"github.com/testcontainers/testcontainers-go/wait"
)

const smokeImage = "postgres:16-alpine"

//go:embed testdata/migrations/*.sql
var smokeMigrationsFS embed.FS

func TestPostgresPackageRoundTrip(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	t.Cleanup(cancel)

	pgContainer, err := tcpostgres.Run(ctx,
		smokeImage,
		tcpostgres.WithDatabase("galaxy_smoke"),
		tcpostgres.WithUsername("galaxy_smoke"),
		tcpostgres.WithPassword("galaxy_smoke"),
		// The Postgres image emits "ready to accept connections" twice during
		// startup: once for the temporary bootstrap instance, once for the real
		// listener on the mapped port. Waiting for the second occurrence
		// prevents racing the bootstrap.
		testcontainers.WithWaitStrategy(
			wait.ForLog("database system is ready to accept connections").
				WithOccurrence(2).
				WithStartupTimeout(60*time.Second),
		),
	)
	if err != nil {
		t.Fatalf("start postgres container: %v", err)
	}
	t.Cleanup(func() {
		if err := testcontainers.TerminateContainer(pgContainer); err != nil {
			t.Errorf("terminate postgres container: %v", err)
		}
	})

	dsn, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
	if err != nil {
		t.Fatalf("postgres connection string: %v", err)
	}

	cfg := postgres.DefaultConfig()
	cfg.PrimaryDSN = dsn
	cfg.OperationTimeout = 5 * time.Second

	db, err := postgres.OpenPrimary(ctx, cfg)
	if err != nil {
		t.Fatalf("open primary: %v", err)
	}
	t.Cleanup(func() {
		if err := db.Close(); err != nil {
			t.Errorf("close db: %v", err)
		}
	})

	if err := postgres.Ping(ctx, db, cfg.OperationTimeout); err != nil {
		t.Fatalf("ping: %v", err)
	}

	migrationsDir, err := fs.Sub(smokeMigrationsFS, "testdata/migrations")
	if err != nil {
		t.Fatalf("sub migrations FS: %v", err)
	}
	if err := postgres.RunMigrations(ctx, db, migrationsDir, "."); err != nil {
		t.Fatalf("run migrations: %v", err)
	}

	var insertedID int64
	if err := db.QueryRowContext(ctx,
		"INSERT INTO smoke (note) VALUES ($1) RETURNING id", "hello",
	).Scan(&insertedID); err != nil {
		t.Fatalf("insert returning id: %v", err)
	}
	if insertedID <= 0 {
		t.Fatalf("inserted id = %d, want > 0", insertedID)
	}

	var note string
	if err := db.QueryRowContext(ctx,
		"SELECT note FROM smoke WHERE id = $1", insertedID,
	).Scan(&note); err != nil {
		t.Fatalf("select note: %v", err)
	}
	if note != "hello" {
		t.Fatalf("note = %q, want %q", note, "hello")
	}
}

func TestOpenReplicasReturnsNilWhenUnconfigured(t *testing.T) {
	t.Parallel()

	cfg := postgres.DefaultConfig()
	cfg.PrimaryDSN = "postgres://localhost:5432/galaxy?sslmode=disable"

	dbs, err := postgres.OpenReplicas(context.Background(), cfg)
	if err != nil {
		t.Fatalf("open replicas: %v", err)
	}
	if dbs != nil {
		t.Fatalf("replicas = %v, want nil", dbs)
	}
}
