package main

import (
	"compress/gzip"
	"embed"
	"encoding/json"
	"io"
	"io/fs"
	"log"
	"mime"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	qrcode "github.com/skip2/go-qrcode"
)

//go:embed static
var rawFS embed.FS

func main() {
	dbPath := os.Getenv("DB_PATH")
	if dbPath == "" {
		dbPath = "queues.db"
	}
	reg, err := openRegistry(dbPath)
	if err != nil {
		log.Fatalf("open registry: %v", err)
	}

	appURL := os.Getenv("APP_URL")

	staticFS, err := fs.Sub(rawFS, "static")
	if err != nil {
		log.Fatal(err)
	}

	mux := http.NewServeMux()

	// ---- About / home page ----
	mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
		serveFile(w, r, staticFS, "about.html")
	})

	// ---- Help / info page ----
	mux.HandleFunc("GET /help.html", func(w http.ResponseWriter, r *http.Request) {
		serveFile(w, r, staticFS, "help.html")
	})

	// ---- Queue list (JSON) ----
	mux.HandleFunc("GET /queues", func(w http.ResponseWriter, r *http.Request) {
		list := reg.List()
		b, _ := json.Marshal(list)
		w.Header().Set("Content-Type", "application/json")
		w.Write(b)
	})

	// ---- Create queue ----
	mux.HandleFunc("POST /new", func(w http.ResponseWriter, r *http.Request) {
		name := sanitize(strings.TrimSpace(r.FormValue("name")), 100)
		if name == "" {
			name = "Очередь"
		}
		inst, err := reg.Create(name)
		if err != nil {
			http.Error(w, "не удалось создать очередь", http.StatusInternalServerError)
			return
		}
		http.Redirect(w, r, "/q/"+inst.ID, http.StatusSeeOther)
	})

	// ---- Queue page ----
	mux.HandleFunc("GET /q/{id}", func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		if reg.Get(id) == nil {
			http.Redirect(w, r, "/", http.StatusSeeOther)
			return
		}
		serveFile(w, r, staticFS, "index.html")
	})

	// ---- Queue WebSocket ----
	mux.HandleFunc("GET /q/{id}/ws", func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		inst := reg.Get(id)
		if inst == nil {
			http.NotFound(w, r)
			return
		}
		inst.hub.wsHandler(w, r)
	})

	// ---- Queue QR code ----
	mux.HandleFunc("GET /q/{id}/qr.png", func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		if reg.Get(id) == nil {
			http.NotFound(w, r)
			return
		}
		url := appURL
		if url == "" {
			scheme := "http"
			if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" {
				scheme = "https"
			}
			url = scheme + "://" + r.Host
			log.Printf("APP_URL not set, using derived URL: %s", url)
		}
		url += "/q/" + id
		png, err := qrcode.Encode(url, qrcode.Medium, 256)
		if err != nil {
			http.Error(w, "qr generation failed", http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "image/png")
		w.Header().Set("Cache-Control", "public, max-age=86400")
		w.Write(png)
	})

	addr := ":8080"
	log.Printf("queue server listening on %s  APP_URL=%q  DB=%q", addr, appURL, dbPath)
	log.Fatal(http.ListenAndServe(addr, mux))
}

// serveFile reads a file from an fs.FS and writes it with gzip if supported.
func serveFile(w http.ResponseWriter, r *http.Request, fsys fs.FS, name string) {
	f, err := fsys.Open(name)
	if err != nil {
		http.NotFound(w, r)
		return
	}
	defer f.Close()
	data, err := io.ReadAll(f)
	if err != nil {
		http.Error(w, "internal error", http.StatusInternalServerError)
		return
	}
	ct := mime.TypeByExtension(filepath.Ext(name))
	if ct == "" {
		ct = http.DetectContentType(data)
	}
	w.Header().Set("Content-Type", ct)
	if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
		w.Header().Set("Content-Encoding", "gzip")
		gz, _ := gzip.NewWriterLevel(w, gzip.BestSpeed)
		defer gz.Close()
		gz.Write(data)
	} else {
		w.Write(data)
	}
}

// ---- Gzip middleware (for future use on static file handler) ----

type gzipResponseWriter struct {
	http.ResponseWriter
	gz *gzip.Writer
}

func (g *gzipResponseWriter) Write(b []byte) (int, error) {
	return g.gz.Write(b)
}

func gzipMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
			next.ServeHTTP(w, r)
			return
		}
		w.Header().Set("Content-Encoding", "gzip")
		w.Header().Del("Content-Length")
		gz, _ := gzip.NewWriterLevel(w, gzip.BestSpeed)
		defer gz.Close()
		next.ServeHTTP(&gzipResponseWriter{w, gz}, r)
	})
}
