package main

import (
	"context"
	"fmt"
	"log"
	"log/slog"
	"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() {
	// Logger estruturado (stdout). Mantemos log.Printf apenas para logs muito
	// iniciais/críticos e para compatibilidade; o restante deve usar slog.
	logger := elinps.LoggerPadrao()
	slog.SetDefault(logger)

	// Carrega .env se existir (conveniência para dev local).
	// Variáveis definidas no SO têm precedência.
	_ = 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()

	// Garante extensões necessárias.
	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(elinps.MiddlewareLogRequisicao(logger))
	r.Use(middleware.Timeout(15 * time.Second))
	r.Use(middleware.Compress(5))

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

	// Limites básicos.
	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)

	// Widget estático.
	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")
		})

		// WASM do widget.
		// Regra: cache controlado por ETag e revalidação obrigatória.
		// Importante: mantemos o MESMO ETag da inicialização (versaoWidget)
		// para JS e WASM, garantindo que ambos "andem juntos".
		r.Get("/e-li.nps.wasm", func(w http.ResponseWriter, r *http.Request) {
			etag := fmt.Sprintf("\"%s\"", versaoWidget)
			w.Header().Set("ETag", etag)
			w.Header().Set("Cache-Control", "no-cache, must-revalidate")
			w.Header().Set("Content-Type", "application/wasm")

			if r.Header.Get("If-None-Match") == etag {
				w.WriteHeader(http.StatusNotModified)
				return
			}
			http.ServeFile(w, r, "web/static/e-li.nps.wasm")
		})

		// Runtime JS do Go para WASM.
		// Também fica sob ETag + revalidação.
		r.Get("/wasm_exec.js", func(w http.ResponseWriter, r *http.Request) {
			etag := fmt.Sprintf("\"%s\"", versaoWidget)
			w.Header().Set("ETag", etag)
			w.Header().Set("Cache-Control", "no-cache, must-revalidate")
			w.Header().Set("Content-Type", "application/javascript")

			if r.Header.Get("If-None-Match") == etag {
				w.WriteHeader(http.StatusNotModified)
				return
			}
			http.ServeFile(w, r, "web/static/wasm_exec.js")
		})
		r.Handle("/*", http.StripPrefix("/static/", fileServer))
	})
	// Conveniência: permitir /teste.html
	r.Get("/teste.html", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "web/static/teste.html")
	})

	// Conveniência: favicon.
	// O arquivo fica em web/static/favicon.ico.
	r.Get("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "web/static/favicon.ico")
	})

	// Rotas NPS.
	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.
	// 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() {
		logger.Info("servidor_iniciado", "addr", 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 {
		logger.Error("erro_no_shutdown", "err", err)
	}
	logger.Info("servidor_finalizado")
}

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
}
