package main

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

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
)

func main() {
	logger := slog.New(slog.NewJSONHandler(os.Stderr, nil))
	if err := run(logger); err != nil {
		logger.Error("stopped", "error", err.Error())
		os.Exit(1)
	}
}

func run(logger *slog.Logger) error {
	if done, err := commandHelp(os.Args[1:], os.Stdout); done {
		return err
	}
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()
	if len(os.Args) > 1 {
		if len(os.Args) != 2 || os.Args[1] != "migrate" {
			return fmt.Errorf("usage: contentd [migrate]")
		}
		conn, err := pgx.Connect(ctx, databaseURL(os.Getenv("DATABASE_URL")))
		if err != nil {
			return fmt.Errorf("migration database connection failed")
		}
		defer conn.Close(context.Background())
		if err = migrate(ctx, conn); err != nil {
			return err
		}
		logger.Info("schema ready", "version", 1)
		return nil
	}
	c, err := loadConfig(os.Getenv)
	if err != nil {
		return err
	}
	config, err := pgxpool.ParseConfig(c.Database)
	if err != nil {
		return fmt.Errorf("invalid DATABASE_URL")
	}
	config.MaxConns = int32(c.Connections)
	config.ConnConfig.ConnectTimeout = 5 * time.Second
	if c.Trace != "" {
		config.ConnConfig.Tracer = SQLTracer{}
	}
	pool, err := pgxpool.NewWithConfig(ctx, config)
	if err != nil {
		return fmt.Errorf("database pool configuration failed")
	}
	defer pool.Close()
	if err = pool.Ping(ctx); err != nil {
		return fmt.Errorf("database unavailable at startup")
	}
	if c.Profile == "production" {
		var hash string
		if err = pool.QueryRow(ctx, "SELECT sha256 FROM content_schema_migrations WHERE version=1 AND (SELECT count(*) FROM content_schema_migrations)=1").Scan(&hash); err != nil || hash != schemaHash() {
			return fmt.Errorf("schema version/checksum unavailable or incompatible; run migrate first")
		}
	}
	app := &Application{store: &Store{pool: pool}, production: c.Profile == "production"}
	var handler http.Handler = app
	if c.Trace != "" {
		handler = traceHTTP(handler, c.Trace)
	}
	server := &http.Server{Addr: c.Address}
	if c.Profile == "production" {
		handler = &operationalHandler{next: handler, ready: pool.Ping, hosts: c.Hosts, slots: make(chan struct{}, c.Inflight), logger: logger}
		server.ReadHeaderTimeout = 5 * time.Second
		server.ReadTimeout = 15 * time.Second
		server.WriteTimeout = 30 * time.Second
		server.IdleTimeout = 60 * time.Second
		server.MaxHeaderBytes = 32 << 10
	}
	server.Handler = handler
	stop, stopSignals := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stopSignals()
	done := make(chan error, 1)
	go func() { done <- server.ListenAndServe() }()
	logger.Info("starting", "profile", c.Profile, "pid", os.Getpid(), "address", c.Address)
	select {
	case err = <-done:
		if err != http.ErrServerClosed {
			return err
		}
	case <-stop.Done():
		drain, end := context.WithTimeout(context.Background(), 10*time.Second)
		defer end()
		if err = server.Shutdown(drain); err != nil {
			server.Close()
			return fmt.Errorf("graceful shutdown exceeded deadline")
		}
		if err = <-done; err != http.ErrServerClosed {
			return err
		}
	}
	logger.Info("stopped cleanly")
	return nil
}
