// Package katalog adalah REST API sederhana untuk daftar item.
//
// Konfigurasi dibaca dari environment variable (pola twelve-factor):
// PORT menentukan port HTTP, sedangkan PG* menentukan koneksi PostgreSQL.
package main

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

// konfigurasi menyimpan seluruh nilai yang dibaca dari environment.
type konfigurasi struct {
	Port string
	DSN  string
}

// bacaKonfigurasi mengambil PORT dan membangun DSN dari variabel PG*.
// Nilai default dipilih agar aplikasi juga bisa jalan di luar container.
func bacaKonfigurasi() konfigurasi {
	ambil := func(kunci, standar string) string {
		if v := os.Getenv(kunci); v != "" {
			return v
		}
		return standar
	}
	dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
		ambil("PGHOST", "localhost"),
		ambil("PGPORT", "5432"),
		ambil("PGUSER", "katalog"),
		os.Getenv("PGPASSWORD"),
		ambil("PGDATABASE", "katalog"),
	)
	return konfigurasi{
		Port: ambil("PORT", "8080"),
		DSN:  dsn,
	}
}

// routes memetakan endpoint ke handler dengan pola routing Go 1.22+.
func routes(db *sql.DB) http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /healthz", handleHealthz(db))
	mux.HandleFunc("GET /api/items", handleListItems(db))
	mux.HandleFunc("POST /api/items", handleCreateItem(db))
	mux.HandleFunc("GET /api/items/{id}", handleGetItem(db))
	return mux
}

func main() {
	cfg := bacaKonfigurasi()

	// Context dibatalkan saat proses menerima SIGINT atau SIGTERM.
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	db, err := Open(ctx, cfg.DSN)
	if err != nil {
		log.Fatalf("membuka database: %v", err)
	}
	defer db.Close()

	if err := migrate(ctx, db); err != nil {
		log.Fatalf("menjalankan migrasi: %v", err)
	}

	srv := &http.Server{
		Addr:              ":" + cfg.Port,
		Handler:           routes(db),
		ReadHeaderTimeout: 5 * time.Second,
	}

	// Saat sinyal berakhir diterima, beri waktu 10 detik untuk
	// menyelesaikan request yang sedang diproses (graceful shutdown).
	go func() {
		<-ctx.Done()
		log.Println("sinyal berakhir diterima, mematikan server...")
		shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		if err := srv.Shutdown(shutdownCtx); err != nil {
			log.Printf("shutdown: %v", err)
		}
	}()

	log.Printf("server mendengarkan di :%s", cfg.Port)
	if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
		log.Fatalf("server: %v", err)
	}
}
