// Package postgres opens the backend's primary Postgres pool and applies the
// embedded migrations.
//
// The package is a thin wrapper around galaxy/postgres: it adapts the backend
// configuration shape to galaxy/postgres.Config, plumbs the OpenTelemetry
// tracer and meter providers from the telemetry runtime, instruments the
// pool, and verifies connectivity with a bounded Ping.
package postgres

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	"galaxy/backend/internal/config"
	"galaxy/backend/internal/postgres/migrations"
	"galaxy/backend/internal/telemetry"

	pgshared "galaxy/postgres"
)

// connMaxLifetime caps the lifetime of an individual pooled connection. Kept
// in sync with galaxy/postgres.DefaultConnMaxLifetime so behaviour matches
// the helper's defaults until backend has reason to deviate.
const connMaxLifetime = 30 * time.Minute

// Open constructs the primary Postgres pool, instruments it, pings it, and
// returns the *sql.DB. Closing the database is the caller's responsibility.
func Open(ctx context.Context, cfg config.PostgresConfig, runtime *telemetry.Runtime) (*sql.DB, error) {
	pgCfg := pgshared.Config{
		PrimaryDSN:       cfg.DSN,
		OperationTimeout: cfg.OperationTimeout,
		MaxOpenConns:     cfg.MaxConns,
		MaxIdleConns:     cfg.MinConns,
		ConnMaxLifetime:  connMaxLifetime,
	}

	db, err := pgshared.OpenPrimary(
		ctx,
		pgCfg,
		pgshared.WithTracerProvider(runtime.TracerProvider()),
		pgshared.WithMeterProvider(runtime.MeterProvider()),
	)
	if err != nil {
		return nil, fmt.Errorf("open backend postgres pool: %w", err)
	}

	if _, err := pgshared.InstrumentDBStats(
		db,
		pgshared.WithTracerProvider(runtime.TracerProvider()),
		pgshared.WithMeterProvider(runtime.MeterProvider()),
	); err != nil {
		_ = db.Close()
		return nil, fmt.Errorf("instrument backend postgres pool: %w", err)
	}

	if err := pgshared.Ping(ctx, db, cfg.OperationTimeout); err != nil {
		_ = db.Close()
		return nil, fmt.Errorf("ping backend postgres pool: %w", err)
	}

	return db, nil
}

// schemaName is the Postgres schema owned by the backend service. Every
// backend table lives here.
const schemaName = "backend"

// ApplyMigrations runs every pending Up migration embedded in the backend
// binary against db. The schema is created upfront so goose's bookkeeping
// table (`goose_db_version`, scoped to the DSN `search_path = backend`)
// has somewhere to land before the first migration runs; migration
// `00001_init.sql` re-asserts the schema with `IF NOT EXISTS`, so the
// double-create is idempotent.
func ApplyMigrations(ctx context.Context, db *sql.DB) error {
	if _, err := db.ExecContext(ctx, "CREATE SCHEMA IF NOT EXISTS "+schemaName); err != nil {
		return fmt.Errorf("ensure backend schema: %w", err)
	}
	if err := pgshared.RunMigrations(ctx, db, migrations.Migrations(), "."); err != nil {
		return fmt.Errorf("apply backend migrations: %w", err)
	}
	return nil
}
