package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"syscall"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/joho/godotenv"

	"e-li.nps/internal/db"
	elinps "e-li.nps/internal/elinps"
)

func main() {
	// Load .env if present (convenience for local dev). Environment variables
	// explicitly set in the OS take precedence.
	_ = godotenv.Load()

	cfg := mustLoadConfig()

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	pool, err := db.NewPool(ctx, cfg.DatabaseURL)
	if err != nil {
		log.Fatalf("db connect: %v", err)
	}
	defer pool.Close()

	// Ensures required extensions exist.
	if err := db.EnsurePgcrypto(ctx, pool); err != nil {
		log.Fatalf("ensure pgcrypto: %v", err)
	}

	r := chi.NewRouter()
	r.Use(middleware.RequestID)
	r.Use(middleware.RealIP)
	r.Use(middleware.Recoverer)
	r.Use(middleware.Timeout(15 * time.Second))
	r.Use(middleware.Compress(5))

	// CORS wildcard + preflight
	r.Use(elinps.CORSMiddleware())

	// Basic limits
	r.Use(elinps.MaxBodyBytesMiddleware(64 * 1024))

	// Health
	r.Get("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })

	// Home: renderiza README.md
	// Público (sem senha), para facilitar documentação do serviço.
	r.Get("/", elinps.NewReadmePage("README.md").ServeHTTP)

	// Static widget
	fileServer := http.FileServer(http.Dir("web/static"))
	// Versão do widget para controle de cache.
	//
	// Regra do projeto: a versão é gerada a cada inicialização do servidor.
	// Isso evita que o browser continue usando um gonps.js antigo após uma
	// atualização e o usuário final veja comportamento quebrado.
	versaoWidget := strconv.FormatInt(time.Now().UnixNano(), 10)

	r.Route("/static", func(r chi.Router) {
		r.Get("/e-li.nps.js", func(w http.ResponseWriter, r *http.Request) {
			// Estratégia: permitir cache local, mas obrigar revalidação.
			// Quando a versão mudar, o ETag muda e o browser baixa o JS novo.
			etag := fmt.Sprintf("\"%s\"", versaoWidget)
			w.Header().Set("ETag", etag)
			w.Header().Set("Cache-Control", "no-cache, must-revalidate")

			// Se o cliente já tem essa versão, evitamos enviar o arquivo novamente.
			if r.Header.Get("If-None-Match") == etag {
				w.WriteHeader(http.StatusNotModified)
				return
			}

			http.ServeFile(w, r, "web/static/e-li.nps.js")
		})
		r.Handle("/*", http.StripPrefix("/static/", fileServer))
	})
	// Convenience: allow /teste.html
	r.Get("/teste.html", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "web/static/teste.html")
	})

	// NPS routes
	h := elinps.NewHandlers(pool)
	r.Route("/api/e-li.nps", func(r chi.Router) {
		r.Post("/pedido", h.PostPedido)
		r.Patch("/{produto}/{id}", h.PatchResposta)
	})

	r.Route("/e-li.nps", func(r chi.Router) {
		r.Get("/{produto}/{id}/form", h.GetForm)
	})

	// Painel (dashboard)
	// Protegido por SENHA_PAINEL.
	// Se SENHA_PAINEL estiver vazia, o painel fica desabilitado.
	painel := elinps.NewPainelHandlers(pool, cfg.SenhaPainel)
	r.Mount("/painel", painel.Router())

	srv := &http.Server{
		Addr:              cfg.Addr,
		Handler:           r,
		ReadHeaderTimeout: 5 * time.Second,
	}

	go func() {
		log.Printf("listening on %s", cfg.Addr)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %v", err)
		}
	}()

	<-ctx.Done()
	shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := srv.Shutdown(shutdownCtx); err != nil {
		log.Printf("shutdown: %v", err)
	}
	log.Printf("bye")
}

type config struct {
	Addr        string
	DatabaseURL string
	SenhaPainel string
}

func mustLoadConfig() config {
	cfg := config{
		Addr:        envOr("ADDR", ":8080"),
		DatabaseURL: os.Getenv("DATABASE_URL"),
		SenhaPainel: os.Getenv("SENHA_PAINEL"),
	}
	if cfg.DatabaseURL == "" {
		fmt.Fprintln(os.Stderr, "missing DATABASE_URL")
		os.Exit(2)
	}
	return cfg
}

func envOr(k, def string) string {
	v := os.Getenv(k)
	if v == "" {
		return def
	}
	return v
}
