package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"sync/atomic"
)

// Middleware membungkus satu http.Handler menjadi http.Handler baru.
// Bentuk func type seperti ini memudahkan perangkaian middleware.
type Middleware func(http.Handler) http.Handler

// Chain menyusun middleware di sekeliling handler dasar. Middleware yang
// ditulis lebih dulu berada di lapisan paling luar, sehingga urutan
// Chain(mux, A, B, C) menghasilkan pemanggilan A → B → C → mux.
func Chain(base http.Handler, mws ...Middleware) http.Handler {
	h := base
	for i := len(mws) - 1; i >= 0; i-- {
		h = mws[i](h)
	}
	return h
}

// kunciRequestID adalah tipe unexported untuk kunci context. Tipe khusus
// mencegah tabrakan kunci dengan package lain yang menyimpan nilai context.
type kunciRequestID struct{}

// RequestIDFrom mengambil request ID dari context.
func RequestIDFrom(ctx context.Context) string {
	id, _ := ctx.Value(kunciRequestID{}).(string)
	return id
}

// NewRequestID membuat middleware yang menandai setiap permintaan dengan
// ID unik. ID disimpan di context dan di header respons X-Request-ID
// sehingga satu permintaan dapat dilacak dari log sampai ke pemanggil.
//
// Pada proyek ini ID dibuat dari penghitung sederhana agar output demo dan
// pengujian bersifat deterministik. Pada produksi, ganti generator dengan
// nilai acak dari crypto/rand.
func NewRequestID() Middleware {
	var hitung atomic.Uint64
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			id := fmt.Sprintf("req-%03d", hitung.Add(1))
			w.Header().Set("X-Request-ID", id)
			ctx := context.WithValue(r.Context(), kunciRequestID{}, id)
			next.ServeHTTP(w, r.WithContext(ctx))
		})
	}
}

// perekamStatus membungkus http.ResponseWriter untuk menangkap status code
// yang ditulis handler. Tanpa pembungkus ini, middleware tidak bisa tahu
// status akhir karena WriteHeader dipanggil di lapisan dalam.
type perekamStatus struct {
	http.ResponseWriter
	status int
}

func (p *perekamStatus) WriteHeader(code int) {
	p.status = code
	p.ResponseWriter.WriteHeader(code)
}

// NewLogging membuat middleware yang mencatat setiap permintaan selesai
// diproses: request ID, metode, path, dan status respons.
func NewLogging(logger io.Writer) Middleware {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			rec := &perekamStatus{ResponseWriter: w, status: http.StatusOK}
			next.ServeHTTP(rec, r)
			fmt.Fprintf(logger, "%s %s %s %d\n",
				RequestIDFrom(r.Context()), r.Method, r.URL.Path, rec.status)
		})
	}
}

// NewRecover membuat middleware yang menangkap panic dari handler,
// mencatatnya, lalu mengirim respons 500. Tanpa middleware ini, satu
// handler yang panic akan memutus koneksi secara kasar.
func NewRecover(logger io.Writer) Middleware {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			defer func() {
				if err := recover(); err != nil {
					fmt.Fprintf(logger, "PANIC %s: %v\n",
						RequestIDFrom(r.Context()), err)
					w.WriteHeader(http.StatusInternalServerError)
					fmt.Fprintln(w, "kesalahan internal server")
				}
			}()
			next.ServeHTTP(w, r)
		})
	}
}
