package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/go-chi/cors"
	"github.com/golang-jwt/jwt/v5"
)

type (
	Foetel = string
	Leves  = string
	Koret  = string
)

type Valasztek struct {
	Foetelek []Foetel `json:"foetelek"`
	Levesek  []Leves  `json:"levesek"`
	Koretek  []Koret  `json:"koretek"`
}

type Menu struct {
	Foetelek []Foetel
	Levesek  []Leves
	Koretek  []Koret
}

type Order struct {
	ID        int    `json:"id"`
	Username  string `json:"username"`
	Soup      string `json:"soup"`
	Main      string `json:"main"`
	Side      string `json:"side"`
	CreatedAt string `json:"created_at"`
	Status    string `json:"status"`
}

type FinalizedSummary struct {
	Pickup  []string `json:"pickup"`
	Kitchen []string `json:"kitchen"`
}

type App struct {
	DB             *sql.DB
	Broker         *Broker
	FinalizeUpdate chan struct{} // notify scheduler to reload time
	LastSummary    *FinalizedSummary
}

type Broker struct {
	clients   map[chan []byte]bool
	add       chan chan []byte
	remove    chan chan []byte
	broadcast chan []byte
}

type registerRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

type loginRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

type selectionRequest struct {
	Main string `json:"main"`
	Side string `json:"side"`
	Soup string `json:"soup"`
}

var jwtSecret = []byte("supersecretkey") // TODO: move to env variable

func generateToken(userID int, username string) (string, error) {
	claims := jwt.MapClaims{
		"user_id":  userID,
		"username": username,
		"exp":      time.Now().Add(2 * time.Hour).Unix(),
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(jwtSecret)
}

func writeJSON(w http.ResponseWriter, v any) {
	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(v); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func (app *App) handleRegister(w http.ResponseWriter, r *http.Request) {
	var req registerRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid request", http.StatusBadRequest)
		return
	}

	if req.Username == "" || req.Password == "" {
		http.Error(w, "username and password required", http.StatusBadRequest)
		return
	}

	res, err := app.DB.Exec(
		"INSERT INTO users (username, password, role) VALUES (?, ?, ?)",
		req.Username, req.Password, 100,
	)
	if err != nil {
		http.Error(w, "username already exists", http.StatusBadRequest)
		return
	}
	id, _ := res.LastInsertId()

	token, err := generateToken(int(id), req.Username)
	if err != nil {
		http.Error(w, "could not generate token", http.StatusInternalServerError)
		return
	}

	writeJSON(w, map[string]string{
		"token":    token,
		"username": req.Username,
	})
}

func (app *App) handleLogin(w http.ResponseWriter, r *http.Request) {
	var req loginRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid request", http.StatusBadRequest)
		return
	}

	var id int
	var storedPassword string
	err := app.DB.QueryRow("SELECT id, password FROM users WHERE username = ?", req.Username).Scan(&id, &storedPassword)
	if err != nil {
		http.Error(w, "invalid username or password", http.StatusUnauthorized)
		return
	}

	if storedPassword != req.Password {
		http.Error(w, "invalid username or password", http.StatusUnauthorized)
		return
	}

	token, err := generateToken(id, req.Username)
	if err != nil {
		http.Error(w, "could not generate token", http.StatusInternalServerError)
		return
	}

	writeJSON(w, map[string]string{
		"token":    token,
		"username": req.Username,
	})
}

func usernameFromJWT(r *http.Request) (string, error) {
	auth := r.Header.Get("Authorization")
	parts := strings.SplitN(auth, " ", 2)
	if len(parts) != 2 || parts[0] != "Bearer" {
		return "", nil
	}
	tokenStr := parts[1]

	token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) { return jwtSecret, nil })
	if err != nil || !token.Valid {
		return "", err
	}
	if claims, ok := token.Claims.(jwt.MapClaims); ok {
		if u, ok := claims["username"].(string); ok {
			return u, nil
		}
	}
	return "", nil
}

func newBroker() *Broker {
	b := &Broker{
		clients:   make(map[chan []byte]bool),
		add:       make(chan chan []byte),
		remove:    make(chan chan []byte),
		broadcast: make(chan []byte),
	}
	go func() {
		for {
			select {
			case c := <-b.add:
				b.clients[c] = true
				log.Printf("SSE client added. Total: %d", len(b.clients))
			case c := <-b.remove:
				if _, ok := b.clients[c]; ok {
					delete(b.clients, c)
					close(c)
					log.Printf("SSE client removed. Total: %d", len(b.clients))
				}
			case msg := <-b.broadcast:
				log.Printf("Broker broadcasting to %d clients", len(b.clients))
				for c := range b.clients {
					select {
					case c <- msg:
						log.Println("Message queued for client")
					default:
						log.Println("Client channel full, skipping")
					}
				}
			}
		}
	}()
	return b
}

func (app *App) handleGetOrders(w http.ResponseWriter, r *http.Request) {
	rows, err := app.DB.Query("SELECT id, username, soup, main, side, created_at, status FROM orders WHERE status = 'active' ORDER BY id DESC")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	var out []Order
	for rows.Next() {
		var o Order
		if err := rows.Scan(&o.ID, &o.Username, &o.Soup, &o.Main, &o.Side, &o.CreatedAt, &o.Status); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		out = append(out, o)
	}
	writeJSON(w, out)
}

func (app *App) handleAddOrder(w http.ResponseWriter, r *http.Request) {
	var in struct{ Soup, Main, Side string }
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil || in.Main == "" || in.Side == "" {
		http.Error(w, "invalid order", http.StatusBadRequest)
		return
	}
	username, err := usernameFromJWT(r)
	if err != nil || username == "" {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	now := time.Now().Format(time.RFC3339)

	// // Step 1: mark any previous active orders for this user as history
	// _, _ = app.DB.Exec("UPDATE orders SET status = 'history' WHERE username = ? AND status = 'active'", username)

	// Step 2: insert new active order
	res, err := app.DB.Exec(
		"INSERT INTO orders (username, soup, main, side, created_at, status) VALUES (?, ?, ?, ?, ?, ?)",
		username, in.Soup, in.Main, in.Side, now, "active",
	)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	id, _ := res.LastInsertId()
	ord := Order{
		ID:        int(id),
		Username:  username,
		Soup:      in.Soup,
		Main:      in.Main,
		Side:      in.Side,
		CreatedAt: now,
		Status:    "active",
	}

	// broadcast to SSE clients
	if data, err := json.Marshal(ord); err == nil {
		log.Printf("Broadcasting active order via SSE: %+v", ord)
		app.Broker.broadcast <- data
	}

	writeJSON(w, ord)
}

func (app *App) handleDeleteOrder(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	username, err := usernameFromJWT(r)
	if err != nil || username == "" {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	res, err := app.DB.Exec("UPDATE orders SET status = 'history' WHERE id = ? AND username = ?", id, username)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	n, _ := res.RowsAffected()
	if n == 0 {
		http.Error(w, "order not found", http.StatusNotFound)
		return
	}

	// broadcast deletion (tell clients to refresh)
	msg := map[string]any{"id": id, "status": "history", "event": "deleted"}
	if data, err := json.Marshal(msg); err == nil {
		app.Broker.broadcast <- data
	}

	writeJSON(w, map[string]string{"status": "archived"})
}

func (app *App) handleOrdersStream(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	w.Header().Del("Content-Encoding")
	w.Header().Set("X-Accel-Buffering", "no")

	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "stream unsupported", http.StatusInternalServerError)
		return
	}

	ch := make(chan []byte, 1)
	app.Broker.add <- ch
	defer func() { app.Broker.remove <- ch }()

	// open the stream
	w.Write([]byte(":ok\n\n"))
	flusher.Flush()

	ticker := time.NewTicker(15 * time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-r.Context().Done():
			log.Println("SSE client disconnected")
			return
		case msg := <-ch:
			log.Printf("SSE send: %s", string(msg))
			w.Write([]byte("data: "))
			w.Write(msg)
			w.Write([]byte("\n\n"))
			flusher.Flush()
		case <-ticker.C:
			// log.Println("SSE heartbeat -> :ping")
			w.Write([]byte(":ping\n\n"))
			flusher.Flush()
		}
	}
}

func (app *App) handleSaveSelection(w http.ResponseWriter, r *http.Request) {
	tokenStr := r.Header.Get("Authorization")
	if tokenStr == "" {
		http.Error(w, "missing token", http.StatusUnauthorized)
		return
	}

	// Strip "Bearer " prefix
	if len(tokenStr) > 7 && tokenStr[:7] == "Bearer " {
		tokenStr = tokenStr[7:]
	}

	token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
		return jwtSecret, nil
	})
	if err != nil || !token.Valid {
		http.Error(w, "invalid token", http.StatusUnauthorized)
		return
	}

	claims := token.Claims.(jwt.MapClaims)
	userID := int(claims["user_id"].(float64))

	var req selectionRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid request", http.StatusBadRequest)
		return
	}

	now := time.Now().Format(time.RFC3339)

	// Insert or update selection (simple approach: delete old one, insert new)
	_, _ = app.DB.Exec("DELETE FROM selections WHERE user_id = ?", userID)
	_, err = app.DB.Exec(
		"INSERT INTO selections (user_id, main, side, soup, created_at) VALUES (?, ?, ?, ?, ?)",
		userID, req.Main, req.Side, req.Soup, now,
	)
	if err != nil {
		http.Error(w, "failed to save selection", http.StatusInternalServerError)
		return
	}

	writeJSON(w, map[string]string{
		"status": "ok",
		"main":   req.Main,
		"side":   req.Side,
		"soup":   req.Soup,
	})
}

func (app *App) handleGetSelection(w http.ResponseWriter, r *http.Request) {
	tokenStr := r.Header.Get("Authorization")
	if tokenStr == "" {
		http.Error(w, "missing token", http.StatusUnauthorized)
		return
	}
	if len(tokenStr) > 7 && tokenStr[:7] == "Bearer " {
		tokenStr = tokenStr[7:]
	}

	token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
		return jwtSecret, nil
	})
	if err != nil || !token.Valid {
		http.Error(w, "invalid token", http.StatusUnauthorized)
		return
	}

	claims := token.Claims.(jwt.MapClaims)
	userID := int(claims["user_id"].(float64))

	row := app.DB.QueryRow("SELECT main, side, soup, created_at FROM selections WHERE user_id = ?", userID)
	var main, side, soup, createdAt string
	err = row.Scan(&main, &side, &soup, &createdAt)
	if err == sql.ErrNoRows {
		writeJSON(w, map[string]string{"status": "none"})
		return
	} else if err != nil {
		http.Error(w, "db error", http.StatusInternalServerError)
		return
	}

	writeJSON(w, map[string]string{
		"main":       main,
		"side":       side,
		"soup":       soup,
		"created_at": createdAt,
	})
}

func (app *App) handleOptions(w http.ResponseWriter, r *http.Request) {
	rows, err := app.DB.Query("SELECT category, name FROM menu_items ORDER BY id ASC")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	valasztek := Valasztek{
		Levesek:  []Leves{},
		Foetelek: []Foetel{},
		Koretek:  []Koret{},
	}

	for rows.Next() {
		var category, name string
		if err := rows.Scan(&category, &name); err == nil {
			switch category {
			case "soup":
				valasztek.Levesek = append(valasztek.Levesek, name)
			case "main":
				valasztek.Foetelek = append(valasztek.Foetelek, name)
			case "side":
				valasztek.Koretek = append(valasztek.Koretek, name)
			}
		}
	}

	writeJSON(w, valasztek)
}

func (app *App) handleGetMenuRaw(w http.ResponseWriter, r *http.Request) {
	rows, err := app.DB.Query("SELECT id, category, name FROM menu_items ORDER BY id ASC")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	var items []map[string]any
	for rows.Next() {
		var id int
		var category, name string
		if err := rows.Scan(&id, &category, &name); err == nil {
			items = append(items, map[string]any{
				"id":       id,
				"category": category,
				"name":     name,
			})
		}
	}

	writeJSON(w, items)
}

func (app *App) getUserRole(username string) (int, error) {
	var role int
	err := app.DB.QueryRow("SELECT role FROM users WHERE username = ?", username).Scan(&role)
	return role, err
}

func (app *App) requireLevel(maxAllowed int) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			username, err := usernameFromJWT(r)
			if err != nil || username == "" {
				http.Error(w, "unauthorized", http.StatusUnauthorized)
				return
			}

			role, err := app.getUserRole(username)
			if err != nil {
				http.Error(w, "db error", http.StatusInternalServerError)
				return
			}

			if role > maxAllowed {
				http.Error(w, "forbidden", http.StatusForbidden)
				return
			}

			next.ServeHTTP(w, r)
		})
	}
}

func (app *App) handleWhoAmI(w http.ResponseWriter, r *http.Request) {
	username, err := usernameFromJWT(r)
	if err != nil || username == "" {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	role, err := app.getUserRole(username)
	if err != nil {
		http.Error(w, "db error", http.StatusInternalServerError)
		return
	}

	writeJSON(w, map[string]any{
		"username": username,
		"role":     role,
	})
}

func (app *App) handleGetUsers(w http.ResponseWriter, r *http.Request) {
	rows, err := app.DB.Query("SELECT id, username, role FROM users ORDER BY id")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	var users []map[string]any
	for rows.Next() {
		var id, role int
		var username string
		rows.Scan(&id, &username, &role)
		users = append(users, map[string]any{"id": id, "username": username, "role": role})
	}

	writeJSON(w, users)
}

func (app *App) handleDeleteUser(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")

	res, err := app.DB.Exec("DELETE FROM users WHERE id = ?", id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	n, _ := res.RowsAffected()
	if n == 0 {
		http.Error(w, "user not found", http.StatusNotFound)
		return
	}

	writeJSON(w, map[string]string{"status": "deleted"})
}

// main.go
func (app *App) handleUpdateUser(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")

	var req struct {
		Username string `json:"username"`
		Password string `json:"password"`
		Role     *int   `json:"role"` // pointer = optional
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid request", http.StatusBadRequest)
		return
	}

	if req.Username == "" && req.Password == "" && req.Role == nil {
		http.Error(w, "nothing to update", http.StatusBadRequest)
		return
	}

	// build dynamic UPDATE
	query := "UPDATE users SET "
	args := []any{}
	first := true
	if req.Username != "" {
		if !first {
			query += ", "
		}
		first = false
		query += "username = ?"
		args = append(args, req.Username)
	}
	if req.Password != "" {
		if !first {
			query += ", "
		}
		first = false
		query += "password = ?"
		args = append(args, req.Password)
	}
	if req.Role != nil {
		if !first {
			query += ", "
		}
		first = false
		query += "role = ?"
		args = append(args, *req.Role)
	}
	query += " WHERE id = ?"
	args = append(args, id)

	res, err := app.DB.Exec(query, args...)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if n, _ := res.RowsAffected(); n == 0 {
		http.Error(w, "user not found", http.StatusNotFound)
		return
	}
	writeJSON(w, map[string]string{"status": "updated"})
}

func (app *App) handleAdminMenu(w http.ResponseWriter, r *http.Request) {
	var in struct {
		Action   string `json:"action"`
		ID       int    `json:"id"`
		Name     string `json:"name"`
		Category string `json:"category"`
	}
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
		http.Error(w, "invalid request", http.StatusBadRequest)
		return
	}

	switch in.Action {
	case "add":
		_, err := app.DB.Exec("INSERT INTO menu_items (category, name) VALUES (?, ?)", in.Category, in.Name)
		if err != nil {
			http.Error(w, err.Error(), 500)
			return
		}
	case "update":
		_, err := app.DB.Exec("UPDATE menu_items SET name = ? WHERE id = ?", in.Name, in.ID)
		if err != nil {
			http.Error(w, err.Error(), 500)
			return
		}
	case "delete":
		_, err := app.DB.Exec("DELETE FROM menu_items WHERE id = ?", in.ID)
		if err != nil {
			http.Error(w, err.Error(), 500)
			return
		}
	}

	// return updated menu
	rows, _ := app.DB.Query("SELECT id, category, name FROM menu_items ORDER BY id ASC")
	defer rows.Close()
	var items []map[string]any
	for rows.Next() {
		var id int
		var category, name string
		rows.Scan(&id, &category, &name)
		items = append(items, map[string]any{"id": id, "category": category, "name": name})
	}
	writeJSON(w, items)
}

func (app *App) handleGetAllOrders(w http.ResponseWriter, r *http.Request) {
	rows, err := app.DB.Query("SELECT id, username, soup, main, side, created_at, status FROM orders ORDER BY id DESC")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	var out []Order
	for rows.Next() {
		var o Order
		if err := rows.Scan(&o.ID, &o.Username, &o.Soup, &o.Main, &o.Side, &o.CreatedAt, &o.Status); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		out = append(out, o)
	}
	writeJSON(w, out)
}

func (app *App) handleAdminUpdateOrderStatus(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")

	var in struct {
		Status string `json:"status"`
	}
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
		http.Error(w, "invalid request", http.StatusBadRequest)
		return
	}

	_, err := app.DB.Exec("UPDATE orders SET status = ? WHERE id = ?", in.Status, id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	switch in.Status {
	case "history":
		// If status is history -> send deleted event
		msg := map[string]any{"id": id, "status": "history", "event": "deleted"}
		if data, err := json.Marshal(msg); err == nil {
			app.Broker.broadcast <- data
		}
	case "active":
		// If status is active -> send new order
		var o Order
		row := app.DB.QueryRow("SELECT id, username, soup, main, side, created_at, status FROM orders WHERE id = ?", id)
		if err := row.Scan(&o.ID, &o.Username, &o.Soup, &o.Main, &o.Side, &o.CreatedAt, &o.Status); err == nil {
			if data, err := json.Marshal(o); err == nil {
				app.Broker.broadcast <- data
			}
		}
	}

	writeJSON(w, map[string]string{"status": "ok"})
}

func (app *App) handleAdminDeleteOrder(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")

	// Step 1: set to history (so user views update immediately)
	_, _ = app.DB.Exec("UPDATE orders SET status = 'history' WHERE id = ?", id)

	// Step 2: broadcast history update
	msg := map[string]any{"id": id, "status": "history", "event": "deleted"}
	if data, err := json.Marshal(msg); err == nil {
		app.Broker.broadcast <- data
	}

	// Step 3: hard delete from DB
	_, err := app.DB.Exec("DELETE FROM orders WHERE id = ?", id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	writeJSON(w, map[string]string{"status": "deleted"})
}

// GET current finalize time
func (app *App) handleGetFinalizeTime(w http.ResponseWriter, r *http.Request) {
	var t string
	err := app.DB.QueryRow("SELECT value FROM settings WHERE key = 'finalize_time'").Scan(&t)
	if err == sql.ErrNoRows {
		t = "10:30" // default if not set
	}
	writeJSON(w, map[string]string{"time": t})
}

// POST new finalize time
func (app *App) handleSetFinalizeTime(w http.ResponseWriter, r *http.Request) {
	var in struct {
		Time string `json:"time"`
	}
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
		http.Error(w, "bad request", 400)
		return
	}

	_, _ = app.DB.Exec(`INSERT OR REPLACE INTO settings (key,value) VALUES ('finalize_time', ?)`, in.Time)

	// notify scheduler
	select {
	case app.FinalizeUpdate <- struct{}{}:
	default: // don’t block if already queued
	}

	writeJSON(w, map[string]string{"status": "ok"})
}

// POST trigger finalize now
func (app *App) handleFinalizeNow(w http.ResponseWriter, r *http.Request) {
	if err := finalizeOrders(app); err != nil {
		http.Error(w, "failed", 500)
		return
	}
	writeJSON(w, map[string]string{"status": "done"})
}

func (app *App) getSetting(key, def string) string {
	var v string
	err := app.DB.QueryRow("SELECT value FROM settings WHERE key = ?", key).Scan(&v)
	if err != nil || v == "" {
		return def
	}
	return v
}

func startDailyCleanup(app *App) {
	loc, _ := time.LoadLocation("Europe/Budapest")

	go func() {
		for {
			// read finalize_time from DB (default "10:30")
			t := app.getSetting("finalize_time", "10:30")

			// parse "HH:MM"
			parts := strings.Split(t, ":")
			hour, _ := strconv.Atoi(parts[0])
			minute, _ := strconv.Atoi(parts[1])

			now := time.Now().In(loc)
			next := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, loc)
			if !next.After(now) {
				next = next.Add(24 * time.Hour)
			}

			log.Println("Next finalize scheduled at", next)

			timer := time.NewTimer(time.Until(next))

			select {
			case <-timer.C:
				if err := finalizeOrders(app); err != nil {
					log.Println("daily finalize failed:", err)
				} else {
					log.Println("orders finalized at", time.Now().In(loc))
				}
			case <-app.FinalizeUpdate:
				log.Println("Finalize time updated, recalculating…")
				timer.Stop()
			}
		}
	}()
}

func finalizeOrders(app *App) error {
	rows, err := app.DB.Query("SELECT id, username, soup, main, side FROM orders WHERE status = 'active'")
	if err != nil {
		return err
	}
	defer rows.Close()

	var summary []Order
	for rows.Next() {
		var o Order
		if err := rows.Scan(&o.ID, &o.Username, &o.Soup, &o.Main, &o.Side); err == nil {
			summary = append(summary, o)
		}
	}

	// Step 2: send summary (log, email, or message)
	sendSummary(app, summary)

	// Step 3: archive
	_, err = app.DB.Exec("UPDATE orders SET status = 'history' WHERE status = 'active'")

	// Step 4: broadcast deletions to SSE clients
	for _, o := range summary {
		msg := map[string]any{"id": o.ID, "status": "history", "event": "deleted"}
		if data, err := json.Marshal(msg); err == nil {
			app.Broker.broadcast <- data
		}
		time.Sleep(20 * time.Millisecond)
	}

	return err
}

func sendSummary(app *App, orders []Order) *FinalizedSummary {
	// Pickup view
	userMap := make(map[string][]string)
	for _, o := range orders {
		items := []string{}
		if o.Soup != "" {
			items = append(items, o.Soup)
		}
		items = append(items, o.Main, o.Side)
		userMap[o.Username] = append(userMap[o.Username], items...)
	}
	pickup := []string{}
	for user, items := range userMap {
		pickup = append(pickup, fmt.Sprintf("%s: [%s]", user, strings.Join(items, ", ")))
	}

	// Kitchen view
	kitchenMap := make(map[string]int)
	for _, o := range orders {
		if o.Soup != "" {
			kitchenMap[o.Soup]++
		}
		kitchenMap[o.Main]++
		kitchenMap[o.Side]++
	}
	kitchen := []string{}
	for item, count := range kitchenMap {
		kitchen = append(kitchen, fmt.Sprintf("%s: %d", item, count))
	}

	summary := &FinalizedSummary{Pickup: pickup, Kitchen: kitchen}
	app.LastSummary = summary

	// Log
	log.Println("=== Pickup view ===")
	for _, line := range pickup {
		log.Println(line)
	}
	log.Println("=== Kitchen view ===")
	for _, line := range kitchen {
		log.Println(line)
	}

	return summary
}

func (app *App) handleGetLastSummary(w http.ResponseWriter, r *http.Request) {
	if app.LastSummary == nil {
		writeJSON(w, map[string]string{"status": "none"})
		return
	}
	writeJSON(w, app.LastSummary)
}

func server(app *App) *http.Server {
	r := chi.NewRouter()

	// Middleware
	r.Use(middleware.RequestID)
	r.Use(middleware.Recoverer)
	r.Use(middleware.Logger)
	r.Use(cors.Handler(cors.Options{
		AllowedOrigins:   []string{"*"},
		AllowedMethods:   []string{"GET", "POST", "OPTIONS"},
		AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type"},
		AllowCredentials: true,
		MaxAge:           300,
	}))

	// Routes (bind methods to app)
	r.Post("/api/register", app.handleRegister)
	r.Post("/api/login", app.handleLogin)
	r.Get("/api/options", app.handleOptions)
	r.Post("/api/selection", app.handleSaveSelection)
	r.Get("/api/selection", app.handleGetSelection)
	r.Get("/api/orders", app.handleGetOrders)
	r.Post("/api/orders", app.handleAddOrder)
	r.Get("/api/orders/stream", app.handleOrdersStream)
	r.Delete("/api/orders/{id}", app.handleDeleteOrder)
	r.Get("/api/me", app.handleWhoAmI)
	r.Get("/api/finalize/time", app.handleGetFinalizeTime)

	// Only role 0 and 1 allowed
	r.Route("/api/admin", func(r chi.Router) {
		r.Use(app.requireLevel(1))
		r.Get("/users", app.handleGetUsers)
		r.Post("/menu", app.handleAdminMenu)
		r.Get("/menu", app.handleGetMenuRaw)
		r.Get("/users", app.handleGetUsers)
		r.Delete("/users/{id}", app.handleDeleteUser)
		r.Put("/users/{id}", app.handleUpdateUser)
		r.Get("/orders", app.handleGetAllOrders)
		r.Put("/orders/{id}/status", app.handleAdminUpdateOrderStatus)
		r.Delete("/orders/{id}", app.handleAdminDeleteOrder)
		r.Get("/finalize/time", app.handleGetFinalizeTime)
		r.Post("/finalize/time", app.handleSetFinalizeTime)
		r.Post("/finalize/now", app.handleFinalizeNow)
		r.Get("/finalize/last", app.handleGetLastSummary)
	})

	// Moderators (role <= 50)
	r.Route("/api/mod", func(r chi.Router) {
		r.Use(app.requireLevel(50))
		r.Post("/menu", app.handleAdminMenu) // menu editor
		r.Get("/menu", app.handleGetMenuRaw)
		r.Get("/finalize/time", app.handleGetFinalizeTime)
		r.Post("/finalize/time", app.handleSetFinalizeTime)
		r.Post("/finalize/now", app.handleFinalizeNow)
		r.Get("/finalize/last", app.handleGetLastSummary)
	})

	// Return configured server
	return &http.Server{
		Addr:    "0.0.0.0:7153",
		Handler: r,
	}
}

func main() {
	db := database()
	defer db.Close()

	app := &App{
		DB:             db,
		Broker:         newBroker(),
		FinalizeUpdate: make(chan struct{}, 1),
	}

	// Create server
	srv := server(app)
	go startDailyCleanup(app)

	log.Println("Server listening on", srv.Addr)
	if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
		log.Fatal(err)
	}
}
