package main

import (
	"context"
	"html/template"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"
)

var templates = make(map[string]*template.Template)

// contextKey is used to pass per-request log extras from handlers to the logging middleware.
type contextKey struct{}
type logContext struct{ extra string }

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}

	// Parse templates once at startup.
	index, err := template.ParseFiles("templates/base.html", "templates/index.html")
	if err != nil {
		log.Fatalf("failed to parse index template: %v", err)
	}
	templates["index"] = index

	waitlist, err := template.ParseFiles("templates/partials/waitlist_confirmation.html")
	if err != nil {
		log.Fatalf("failed to parse waitlist template: %v", err)
	}
	templates["waitlist"] = waitlist

	mux := http.NewServeMux()

	// Serve static assets
	fs := http.FileServer(http.Dir("static"))
	mux.Handle("/static/", http.StripPrefix("/static/", fs))

	// Page routes
	mux.HandleFunc("/", handleIndex)
	mux.HandleFunc("/healthz", handleHealthz)

	// HTMX partial routes
	mux.HandleFunc("/partials/waitlist", handleWaitlist)

	// Wrap with logging (outermost) and security headers (innermost).
	handler := loggingMiddleware(securityHeaders(mux))

	srv := &http.Server{
		Addr:         ":" + port,
		Handler:      handler,
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 30 * time.Second,
		IdleTimeout:  60 * time.Second,
	}

	// Graceful shutdown on SIGINT / SIGTERM.
	go func() {
		sig := make(chan os.Signal, 1)
		signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
		<-sig
		log.Println("shutting down…")
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		if err := srv.Shutdown(ctx); err != nil {
			log.Printf("shutdown error: %v", err)
		}
	}()

	log.Printf("Arcline Project server starting on :%s", port)
	if err := srv.ListenAndServe(); err != http.ErrServerClosed {
		log.Fatalf("server failed: %v", err)
	}
	log.Println("server stopped")
}

// securityHeaders adds basic security headers to every response.
func securityHeaders(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("X-Content-Type-Options", "nosniff")
		w.Header().Set("X-Frame-Options", "DENY")
		w.Header().Set("Referrer-Policy", "no-referrer")
		w.Header().Set("Permissions-Policy", "camera=(), microphone=(), geolocation=()")
		next.ServeHTTP(w, r)
	})
}

// responseWriter wraps http.ResponseWriter to capture the status code and bytes written.
type responseWriter struct {
	http.ResponseWriter
	status int
	bytes  int
}

func (rw *responseWriter) WriteHeader(code int) {
	rw.status = code
	rw.ResponseWriter.WriteHeader(code)
}

func (rw *responseWriter) Write(b []byte) (int, error) {
	if rw.status == 0 {
		rw.status = http.StatusOK
	}
	n, err := rw.ResponseWriter.Write(b)
	rw.bytes += n
	return n, err
}

// loggingMiddleware logs every request with IP, method, path, status, size, duration, referer, and user-agent.
func loggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		lc := &logContext{}
		ctx := context.WithValue(r.Context(), contextKey{}, lc)
		r = r.WithContext(ctx)

		start := time.Now()
		rw := &responseWriter{ResponseWriter: w, status: 0}
		next.ServeHTTP(rw, r)

		ip := r.RemoteAddr
		if fwd := r.Header.Get("X-Forwarded-For"); fwd != "" {
			ip = fwd
		}

		ref := r.Referer()
		if ref == "" {
			ref = "-"
		}

		ua := r.UserAgent()
		if ua == "" {
			ua = "-"
		}

		duration := time.Since(start)

		if lc.extra != "" {
			log.Printf("%s %s %s → %d (%dB) %v ref:%q ua:%q | %s", ip, r.Method, r.URL.Path, rw.status, rw.bytes, duration, ref, ua, lc.extra)
		} else {
			log.Printf("%s %s %s → %d (%dB) %v ref:%q ua:%q", ip, r.Method, r.URL.Path, rw.status, rw.bytes, duration, ref, ua)
		}
	})
}

func handleHealthz(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("ok"))
}

func handleIndex(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	data := map[string]interface{}{
		"Title": "Arcline Project",
	}
	if err := templates["index"].ExecuteTemplate(w, "base", data); err != nil {
		log.Printf("render error: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
	}
}

func handleWaitlist(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
		return
	}

	if err := r.ParseForm(); err != nil {
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}

	email := strings.TrimSpace(r.FormValue("email"))
	if email == "" || !strings.Contains(email, "@") {
		http.Error(w, "Invalid email address", http.StatusBadRequest)
		return
	}

	// Signal the logging middleware that this was a waitlist signup.
	if lc, ok := r.Context().Value(contextKey{}).(*logContext); ok {
		lc.extra = "signup"
	}

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := templates["waitlist"].Execute(w, map[string]string{"Email": email}); err != nil {
		log.Printf("render error: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
	}
}
