package main

import (
	"context"
	"database/sql"
	"flag"
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	_ "github.com/jackc/pgx/v5/stdlib"

	"git.soup.land/soup/lookbook/internal/handlers"
	"git.soup.land/soup/lookbook/internal/middleware"
	"git.soup.land/soup/lookbook/internal/migrations"
	"git.soup.land/soup/lookbook/internal/static"
	"git.soup.land/soup/sxgo/ssr"
)

const defaultAddr = ":8080"

func main() {
	migrate := flag.Bool("migrate", false, "run database migrations and exit")
	rollback := flag.Bool("rollback", false, "roll back migrations and exit (one step by default)")
	rollbackTarget := flag.Int64("to", -1, "target version for rollback when using -rollback")
	dbURLFlag := flag.String("db-url", "", "database connection URL")
	flag.Parse()

	logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelInfo,
	}))

	dbURL := *dbURLFlag
	if dbURL == "" {
		dbURL = os.Getenv("DATABASE_URL")
	}
	if dbURL == "" {
		dbURL = migrations.DefaultURL
	}

	switch {
	case *migrate && *rollback:
		logger.Error("choose either -migrate or -rollback, not both")
		os.Exit(1)
	case *migrate:
		if err := migrations.Up(context.Background(), dbURL, logger); err != nil {
			logger.Error("migration failed", slog.Any("err", err))
			os.Exit(1)
		}
		return
	case *rollback:
		if err := migrations.Down(context.Background(), dbURL, *rollbackTarget, logger); err != nil {
			logger.Error("rollback failed", slog.Any("err", err))
			os.Exit(1)
		}
		return
	}

	// Check for subcommand
	args := flag.Args()
	if len(args) < 1 {
		printUsage()
		os.Exit(1)
	}

	mode := args[0]
	switch mode {
	case "web":
		runWebServer(dbURL, logger)
	default:
		logger.Error("unknown mode", slog.String("mode", mode))
		printUsage()
		os.Exit(1)
	}
}

func printUsage() {
	fmt.Fprintln(os.Stderr, "Usage:")
	fmt.Fprintln(os.Stderr, "  lookbook web         - Run web server")
	fmt.Fprintln(os.Stderr, "")
	fmt.Fprintln(os.Stderr, "Flags:")
	fmt.Fprintln(os.Stderr, "  -migrate           Run database migrations")
	fmt.Fprintln(os.Stderr, "  -rollback          Roll back migrations")
	fmt.Fprintln(os.Stderr, "  -to <version>      Target version for rollback")
	fmt.Fprintln(os.Stderr, "  -db-url <url>      Database connection URL")
}

func runWebServer(dbURL string, logger *slog.Logger) {
	pending, err := migrations.CheckPending(context.Background(), dbURL, logger)
	if err != nil {
		logger.Warn("could not check migration status", slog.Any("err", err))
	} else if pending > 0 {
		logger.Warn("database has pending migrations",
			slog.Int("pending", pending),
			slog.String("hint", "run 'make migrate' to apply"))
	}

	db, err := sql.Open("pgx", dbURL)
	if err != nil {
		logger.Error("failed to open database", slog.Any("err", err))
		os.Exit(1)
	}
	defer db.Close()

	if err := db.Ping(); err != nil {
		logger.Error("failed to ping database", slog.Any("err", err))
		os.Exit(1)
	}

	rc := &handlers.RequestContext{
		DB:        db,
		Logger:    logger,
		TmplCache: ssr.NewTmplCache(handlers.TemplateFuncs),
	}
	router := handlers.NewRouter(rc)

	router.Handle("GET /", handlers.HandleGetGallery)
	router.Handle("POST /items", handlers.HandlePostItem)
	router.Handle("GET /items/{id}", handlers.HandleGetItem)
	router.Handle("POST /items/{id}/tags", handlers.HandlePostItemTags)
	router.Handle("POST /items/{id}/delete", handlers.HandleDeleteItem)
	router.Handle("POST /items/{id}/refresh", handlers.HandleRefreshItem)
	router.Handle("GET /images/{id}", handlers.HandleGetImage)
	router.Handle("POST /auth/login", handlers.HandlePostAuthLogin)
	router.Handle("POST /auth/logout", handlers.HandlePostAuthLogout)
	router.Handle("GET /auth/status", handlers.HandleGetAuthStatus)
	router.HandleStd("GET /static/{version}/", static.Handler())

	addr := defaultAddr
	if envAddr := os.Getenv("ADDR"); envAddr != "" {
		addr = envAddr
	}

	server := &http.Server{
		Addr:              addr,
		Handler:           middleware.Logging(logger)(router),
		ReadHeaderTimeout: 5 * time.Second,
	}

	go func() {
		logger.Info("listening", slog.String("addr", addr))
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			logger.Error("http server error", slog.Any("err", err))
			os.Exit(1)
		}
	}()

	waitForShutdown(server, logger)
}

func waitForShutdown(server *http.Server, logger *slog.Logger) {
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt, syscall.SIGTERM)

	<-quit
	logger.Info("shutting down")

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	if err := server.Shutdown(ctx); err != nil {
		logger.Error("graceful shutdown failed", slog.Any("err", err))
	}
}
