// koneksi database dan runner migrasi sederhana.
//
// Open() mencoba terhubung hingga 10 kali dengan jeda 500 ms — pola ini
// menutup celah startup saat container database belum sepenuhnya siap.
// migrate() membaca migrations/*.sql secara berurutan dan mencatat versi
// yang sudah diterapkan ke tabel schema_migrations.
package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"

	_ "github.com/jackc/pgx/v5/stdlib" // mendaftarkan driver "pgx" untuk database/sql
)

// jumlahPercobaan dan jedaPercobaan mengatur retry koneksi database.
const (
	jumlahPercobaan = 10
	jedaPercobaan   = 500 * time.Millisecond
)

// Open membuka koneksi PostgreSQL dan memastikan benar-benar hidup
// dengan PingContext sebelum dikembalikan.
func Open(ctx context.Context, dsn string) (*sql.DB, error) {
	var errTerakhir error
	for percobaan := 1; percobaan <= jumlahPercobaan; percobaan++ {
		db, err := sql.Open("pgx", dsn)
		if err == nil {
			if err = db.PingContext(ctx); err == nil {
				return db, nil
			}
			db.Close()
		}
		errTerakhir = err
		log.Printf("koneksi database gagal (percobaan %d/%d): %v", percobaan, jumlahPercobaan, err)
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		case <-time.After(jedaPercobaan):
		}
	}
	return nil, fmt.Errorf("database tidak dapat dijangkau setelah %d percobaan: %w", jumlahPercobaan, errTerakhir)
}

// migrate menerapkan setiap berkas migrations/*.sql yang belum tercatat
// di schema_migrations. Satu berkas berjalan dalam satu transaksi,
// bersama pencatatan versinya, supaya keduanya sukses atau gagal bersama.
func migrate(ctx context.Context, db *sql.DB) error {
	if _, err := db.ExecContext(ctx, `
		CREATE TABLE IF NOT EXISTS schema_migrations (
			version    TEXT PRIMARY KEY,
			applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
		)`); err != nil {
		return fmt.Errorf("membuat tabel schema_migrations: %w", err)
	}

	entri, err := os.ReadDir("migrations")
	if err != nil {
		return fmt.Errorf("membaca direktori migrations: %w", err)
	}

	var berkas []string
	for _, e := range entri {
		if !e.IsDir() && strings.HasSuffix(e.Name(), ".sql") {
			berkas = append(berkas, e.Name())
		}
	}
	sort.Strings(berkas)

	for _, nama := range berkas {
		var sudah bool
		if err := db.QueryRowContext(ctx,
			`SELECT EXISTS (SELECT 1 FROM schema_migrations WHERE version = $1)`, nama,
		).Scan(&sudah); err != nil {
			return fmt.Errorf("memeriksa versi %s: %w", nama, err)
		}
		if sudah {
			log.Printf("migrasi %s sudah diterapkan, dilewati", nama)
			continue
		}

		isi, err := os.ReadFile(filepath.Join("migrations", nama))
		if err != nil {
			return fmt.Errorf("membaca %s: %w", nama, err)
		}

		tx, err := db.BeginTx(ctx, nil)
		if err != nil {
			return fmt.Errorf("memulai transaksi %s: %w", nama, err)
		}
		if _, err := tx.ExecContext(ctx, string(isi)); err != nil {
			tx.Rollback()
			return fmt.Errorf("menjalankan %s: %w", nama, err)
		}
		if _, err := tx.ExecContext(ctx,
			`INSERT INTO schema_migrations (version) VALUES ($1)`, nama,
		); err != nil {
			tx.Rollback()
			return fmt.Errorf("mencatat %s: %w", nama, err)
		}
		if err := tx.Commit(); err != nil {
			return fmt.Errorf("komit %s: %w", nama, err)
		}
		log.Printf("migrasi %s diterapkan", nama)
	}
	return nil
}
