// Command server runs the DriverVault API Server. It is the single gateway
// between clients (web app, phone app, Home Assistant plugin, ESP32 device) and
// the PocketBase database kept behind it — clients never talk to PocketBase
// directly. It also serves the superadmin web panel at the server root.
package main

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

	"drivervault/apiserver/internal/api"
	"drivervault/apiserver/internal/bootstrap"
	"drivervault/apiserver/internal/config"
	"drivervault/apiserver/internal/pb"
)

func main() {
	log.SetFlags(log.LstdFlags | log.Lmsgprefix)
	log.SetPrefix("[api] ")

	cfg := config.Load()

	client := pb.New(cfg.PocketBaseURL, cfg.PocketBaseAdminEmail, cfg.PocketBaseAdminPassword)

	// Authenticate the service account up front so the first request doesn't pay
	// for it. A failure is NOT fatal: the PocketBase connection is editable at
	// runtime from the panel, so a superadmin must be able to log in and fix a
	// bad address or bad credentials.
	if cfg.AdminConfigured() {
		authCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		if err := client.Authenticate(authCtx); err != nil {
			log.Printf("WARNING: PocketBase service account auth failed (%s): %v", cfg.PocketBaseURL, err)
			log.Printf("fix it under Settings → PocketBase in the panel at %s", cfg.Addr)
		} else {
			log.Printf("authenticated to PocketBase at %s", cfg.PocketBaseURL)
		}
		cancel()
	} else {
		log.Println("WARNING: POCKETBASE_ADMIN_EMAIL/PASSWORD unset — management endpoints return 503 until configured")
	}

	// Bring PocketBase up to the expected schema (create missing collections,
	// reconcile existing ones) and ensure the super-admin. Idempotent, so it runs
	// on every boot. Non-fatal: a fresh PocketBase that isn't reachable yet, or a
	// bad service account, must not stop the panel from coming up so a superadmin
	// can log in and fix the connection.
	if cfg.Bootstrap && cfg.AdminConfigured() {
		bootCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
		if err := bootstrap.Run(bootCtx, client, bootstrap.Options{
			UsersCollection:    cfg.UsersCollection,
			SuperAdminEmail:    cfg.SuperAdminEmail,
			SuperAdminPassword: cfg.SuperAdminPassword,
			SuperAdminName:     cfg.SuperAdminName,
		}); err != nil {
			log.Printf("WARNING: bootstrap failed: %v", err)
		} else {
			log.Println("bootstrap: PocketBase schema ready")
		}
		cancel()
	}

	srv := api.New(cfg, client)

	// Not fatal: the plugin settings live in PocketBase, which may not be
	// reachable yet on a cold stack or before a service account is configured.
	// StartPlugins logs the reason and retries in the background; the plugin
	// endpoints answer 503 until the read succeeds.
	_ = srv.StartPlugins()

	httpServer := &http.Server{
		Addr:              cfg.Addr,
		Handler:           srv.Handler(),
		ReadHeaderTimeout: 10 * time.Second,
		IdleTimeout:       60 * time.Second,
	}

	go func() {
		log.Printf("listening on %s (PocketBase: %s, panel at /)", cfg.Addr, cfg.PocketBaseURL)
		if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Fatalf("server error: %v", err)
		}
	}()

	// Graceful shutdown on SIGINT/SIGTERM.
	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
	<-stop
	log.Println("shutting down...")

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	srv.Stop(ctx)
	if err := httpServer.Shutdown(ctx); err != nil {
		log.Printf("shutdown error: %v", err)
	}
	log.Println("stopped")
}
