package main

import (
	"context"
	"database/sql"
	"embed"
	"fmt"
	"io/fs"
	"path/filepath"
	"strings"
	"time"

	// Blank import mendaftarkan driver "pgx" ke database/sql.
	_ "github.com/jackc/pgx/v5/stdlib"
)

// migrationFS menyimpan seluruh berkas SQL di folder migrations ke
// dalam binary, sehingga image Docker tidak perlu menyalin folder ini.
//
//go:embed migrations/*.sql
var migrationFS embed.FS

// OpenDatabase membuka pool koneksi PostgreSQL lalu memastikan
// database benar-benar merespons lewat PingContext. sql.Open bersifat
// malas: tanpa ping, galat koneksi baru muncul saat query pertama.
func OpenDatabase(ctx context.Context, dsn string) (*sql.DB, error) {
	db, err := sql.Open("pgx", dsn)
	if err != nil {
		return nil, fmt.Errorf("membuka koneksi: %w", err)
	}

	pingCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()
	if err := db.PingContext(pingCtx); err != nil {
		db.Close()
		return nil, fmt.Errorf("ping database: %w", err)
	}
	return db, nil
}

// RunMigrations menjalankan seluruh berkas migrations/*.sql yang belum
// diterapkan. Versi tercatat di tabel schema_migrations sehingga
// menjalankan migration dua kali aman (idempoten).
func RunMigrations(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)
	}

	files, err := fs.Glob(migrationFS, "migrations/*.sql")
	if err != nil {
		return fmt.Errorf("membaca daftar migration: %w", err)
	}

	for _, name := range files {
		version := strings.TrimSuffix(filepath.Base(name), ".sql")

		var sudah bool
		err := db.QueryRowContext(ctx,
			`SELECT EXISTS (SELECT 1 FROM schema_migrations WHERE version = $1)`,
			version,
		).Scan(&sudah)
		if err != nil {
			return fmt.Errorf("memeriksa migration %s: %w", version, err)
		}
		if sudah {
			continue
		}

		body, err := migrationFS.ReadFile(name)
		if err != nil {
			return fmt.Errorf("membaca berkas %s: %w", name, err)
		}

		// Satu migration dijalankan dalam satu transaksi bersama
		// pencatatannya, sehingga skema dan daftar versi selalu
		// sejalan walau proses berhenti di tengah jalan.
		tx, err := db.BeginTx(ctx, nil)
		if err != nil {
			return fmt.Errorf("memulai transaksi migration %s: %w", version, err)
		}
		if _, err := tx.ExecContext(ctx, string(body)); err != nil {
			tx.Rollback()
			return fmt.Errorf("menjalankan migration %s: %w", version, err)
		}
		if _, err := tx.ExecContext(ctx,
			`INSERT INTO schema_migrations (version) VALUES ($1)`, version,
		); err != nil {
			tx.Rollback()
			return fmt.Errorf("mencatat migration %s: %w", version, err)
		}
		if err := tx.Commit(); err != nil {
			return fmt.Errorf("mengcommit migration %s: %w", version, err)
		}
	}
	return nil
}
