package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
)

// Middleware membungkus satu handler menjadi handler baru.
type Middleware func(http.Handler) http.Handler

// statusRecorder membungkus ResponseWriter untuk merekam status code.
type statusRecorder struct {
	http.ResponseWriter
	status int
}

func (rec *statusRecorder) WriteHeader(code int) {
	rec.status = code
	rec.ResponseWriter.WriteHeader(code)
}

func loggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		rec := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
		next.ServeHTTP(rec, r)
		fmt.Printf("log: %s %s -> %d\n", r.Method, r.URL.Path, rec.status)
	})
}

func recoverMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if err := recover(); err != nil {
				http.Error(w, fmt.Sprintf("kesalahan internal: %v", err),
					http.StatusInternalServerError)
			}
		}()
		next.ServeHTTP(w, r)
	})
}

func authMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Authorization") != "Bearer token-demo" {
			http.Error(w, "token tidak valid", http.StatusUnauthorized)
			return
		}
		next.ServeHTTP(w, r)
	})
}

// chain menyusun middleware: m0 membungkus m1, m1 membungkus m2, dst.
func chain(h http.Handler, mws ...Middleware) http.Handler {
	for i := len(mws) - 1; i >= 0; i-- {
		h = mws[i](h)
	}
	return h
}

func main() {
	final := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/ledak" {
			panic("boom dari handler")
		}
		fmt.Fprintln(w, "selamat datang di area terproteksi")
	})

	// Urutan bungkus: logging -> recover -> auth -> final.
	app := chain(final, loggingMiddleware, recoverMiddleware, authMiddleware)

	uji(app, "GET", "/items", "dengan token")
	uji(app, "GET", "/items", "tanpa token")
	uji(app, "GET", "/ledak", "dengan token")
}

func uji(h http.Handler, method, path, keterangan string) {
	fmt.Printf("== %s %s (%s)\n", method, path, keterangan)
	req := httptest.NewRequest(method, path, nil)
	if keterangan == "dengan token" {
		req.Header.Set("Authorization", "Bearer token-demo")
	}
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	fmt.Printf("   status: %d | body: %s\n", rec.Code, strings.TrimSpace(rec.Body.String()))
}
