package main

import (
	"encoding/json"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"time"

	httpSwagger "github.com/swaggo/http-swagger/v2"
)

var allowedOrigins []string

func init() {
	raw := envOr("ALLOWED_ORIGINS", "https://dune-admin.layout.tools,http://localhost:5173")
	for _, o := range strings.Split(raw, ",") {
		if o = strings.TrimSpace(o); o != "" {
			allowedOrigins = append(allowedOrigins, o)
		}
	}
}

func originAllowed(origin string) bool {
	for _, o := range allowedOrigins {
		if o == origin {
			return true
		}
	}
	return false
}

// newDirectorProxy builds the /director/ reverse-proxy handler for target. It
// strips the /director prefix before forwarding and routes upstream connections
// through dial (the executor tunnel), so the director is reachable from
// wherever the executor runs rather than the dune-admin host.
func newDirectorProxy(target *url.URL, dial func(network, addr string) (net.Conn, error)) http.HandlerFunc {
	proxy := httputil.NewSingleHostReverseProxy(target)
	proxy.Transport = httpTransportVia(dial)
	return func(w http.ResponseWriter, r *http.Request) {
		r.URL.Path = strings.TrimPrefix(r.URL.Path, "/director")
		if r.URL.Path == "" {
			r.URL.Path = "/"
		}
		r.Host = target.Host
		proxy.ServeHTTP(w, r)
	}
}

// originAllowedForRequest applies the explicit allowlist AND a same-host
// exception: a browser requesting from `http://172.16.12.59:9090/` against the
// dune-admin server running on the same host should not be considered cross-
// origin and never needs to be added to ALLOWED_ORIGINS.
//
// When Origin is absent (non-browser WebSocket clients), the request is allowed
// only if the TCP connection originates from a loopback address. r.RemoteAddr
// is used — not r.Host, which is a client-controlled header and can be spoofed.
func originAllowedForRequest(r *http.Request) bool {
	origin := r.Header.Get("Origin")
	if origin == "" {
		// No Origin header: allow only actual loopback TCP connections.
		remoteHost, _, err := net.SplitHostPort(r.RemoteAddr)
		if err != nil {
			return false
		}
		ip := net.ParseIP(remoteHost)
		return ip != nil && ip.IsLoopback()
	}
	if u, err := url.Parse(origin); err == nil && u.Host == r.Host {
		return true
	}
	return originAllowed(origin)
}

func corsMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		origin := r.Header.Get("Origin")
		w.Header().Set("Vary", "Origin")
		if origin != "" && originAllowedForRequest(r) {
			w.Header().Set("Access-Control-Allow-Origin", origin)
			w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
			w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
			w.Header().Set("Access-Control-Allow-Private-Network", "true")
		}
		if r.Method == http.MethodOptions {
			w.WriteHeader(http.StatusNoContent)
			return
		}
		next.ServeHTTP(w, r)
	})
}

func startServer(addr string) {
	mux := http.NewServeMux()

	// ── status ────────────────────────────────────────────────────────────────
	mux.HandleFunc("GET /api/v1/status", handleStatus)
	mux.HandleFunc("POST /api/v1/reconnect", handleReconnect)
	mux.HandleFunc("GET /api/v1/config", handleGetConfig)
	mux.HandleFunc("POST /api/v1/config", handleSaveConfig)
	mux.HandleFunc("GET /api/v1/update/check", handleUpdateCheck)
	mux.HandleFunc("POST /api/v1/update/apply", handleUpdateApply)

	// ── server settings (UserGame.ini / UserOverrides.ini) ────────────────
	mux.HandleFunc("GET /api/v1/server-settings", handleGetServerSettings)
	mux.HandleFunc("PUT /api/v1/server-settings", handleUpdateServerSettings)
	mux.HandleFunc("PUT /api/v1/server-settings/raw", handleUpdateRawSection)

	// ── director config (Battlegroup Director / map persistence — AMP) ────────
	mux.HandleFunc("GET /api/v1/director-config", handleGetDirectorConfig)
	mux.HandleFunc("PUT /api/v1/director-config", handleUpdateDirectorConfig)

	// ── scheduled restarts ────────────────────────────────────────────────────
	mux.HandleFunc("GET /api/v1/scheduled-restarts", handleGetScheduledRestarts)
	mux.HandleFunc("PUT /api/v1/scheduled-restarts", handleUpdateScheduledRestarts)
	mux.HandleFunc("POST /api/v1/scheduled-restarts/skip-next", handleSkipNextRestart)

	// Database backups (#150) — AMP-native pg_dump/restore + scheduling.
	mux.HandleFunc("GET /api/v1/db-backups", handleDBBackupList)
	mux.HandleFunc("POST /api/v1/db-backups", handleDBBackupCreate)
	mux.HandleFunc("DELETE /api/v1/db-backups", handleDBBackupDelete)
	mux.HandleFunc("GET /api/v1/db-backups/download", handleDBBackupDownload)
	mux.HandleFunc("POST /api/v1/db-backups/restore", handleDBBackupRestore)
	mux.HandleFunc("GET /api/v1/scheduled-backups", handleGetScheduledBackups)
	mux.HandleFunc("PUT /api/v1/scheduled-backups", handleUpdateScheduledBackups)

	// Web interfaces (#155) — operator-configurable links for the Server Health card.
	mux.HandleFunc("GET /api/v1/web-interfaces", handleGetWebInterfaces)
	mux.HandleFunc("PUT /api/v1/web-interfaces", handleUpdateWebInterfaces)

	// ── battlegroup ───────────────────────────────────────────────────────────
	mux.HandleFunc("GET /api/v1/battlegroup/status", handleBGStatus)
	mux.HandleFunc("POST /api/v1/battlegroup/exec", handleBGExec)
	mux.HandleFunc("GET /api/v1/battlegroup/pods", handleBGPods)
	mux.HandleFunc("GET /api/v1/battlegroup/backup-files", handleBGBackupFiles)
	mux.HandleFunc("GET /api/v1/battlegroup/backup-files/download", handleBGBackupDownload)
	mux.HandleFunc("POST /api/v1/battlegroup/backup-files/upload", handleBGBackupUpload)
	mux.HandleFunc("POST /api/v1/battlegroup/restore", handleBGRestore)

	// ── players ───────────────────────────────────────────────────────────────
	mux.HandleFunc("GET /api/v1/players", handleGetPlayers)
	mux.HandleFunc("GET /api/v1/players/online", handleGetOnlineState)
	mux.HandleFunc("GET /api/v1/players/currency", handleGetCurrency)
	mux.HandleFunc("GET /api/v1/players/factions", handleGetFactions)
	mux.HandleFunc("GET /api/v1/players/specs", handleGetSpecs)
	mux.HandleFunc("GET /api/v1/players/summary", handleGetPlayerSummary)
	mux.HandleFunc("GET /api/v1/players/faction-trends", handleGetFactionTrends)
	mux.HandleFunc("GET /api/v1/players/templates", handleGetTemplates)
	mux.HandleFunc("GET /api/v1/players/{id}/inventory", handleGetInventory)
	mux.HandleFunc("GET /api/v1/players/{id}/journey", handleGetJourney)
	mux.HandleFunc("POST /api/v1/players/give-item", handleGiveItem)
	mux.HandleFunc("POST /api/v1/players/give-items", handleGiveItems)
	mux.HandleFunc("POST /api/v1/players/give-currency", handleGiveCurrency)
	mux.HandleFunc("POST /api/v1/players/grant-live", handleGrantLive)
	mux.HandleFunc("POST /api/v1/players/give-faction-rep", handleGiveFactionRep)
	mux.HandleFunc("POST /api/v1/players/give-scrip", handleGiveScrip)
	mux.HandleFunc("POST /api/v1/players/award-xp", handleAwardXP)
	mux.HandleFunc("POST /api/v1/players/award-char-xp", handleAwardCharXP)
	mux.HandleFunc("POST /api/v1/players/award-intel", handleAwardIntel)
	mux.HandleFunc("POST /api/v1/players/rename", handleRenameCharacter)
	mux.HandleFunc("GET /api/v1/players/{id}/tags", handleGetPlayerTags)
	mux.HandleFunc("POST /api/v1/players/update-tags", handleUpdatePlayerTags)
	mux.HandleFunc("POST /api/v1/players/returning-player-award", handleGrantReturningPlayerAward)
	mux.HandleFunc("POST /api/v1/players/dismiss-returning-player-award", handleDismissReturningPlayerAward)
	mux.HandleFunc("GET /api/v1/players/{id}/export", handleCharacterExport)
	mux.HandleFunc("POST /api/v1/players/delete-account", handleDeleteAccount)
	mux.HandleFunc("DELETE /api/v1/players/item/{id}", handleDeleteItem)
	mux.HandleFunc("POST /api/v1/players/reset-spec", handleResetSpec)
	mux.HandleFunc("POST /api/v1/players/set-faction-tier", handleSetFactionTier)
	mux.HandleFunc("POST /api/v1/players/progression-unlock", handleProgressionUnlock)
	mux.HandleFunc("POST /api/v1/players/progression-reverse", handleProgressionReverse)
	mux.HandleFunc("GET /api/v1/progression/presets", handleListProgressionPresets)
	mux.HandleFunc("POST /api/v1/players/progression/apply-preset", handleApplyProgressionPreset)
	mux.HandleFunc("POST /api/v1/players/journey/complete", handleJourneyComplete)
	mux.HandleFunc("POST /api/v1/players/journey/reset", handleJourneyReset)
	mux.HandleFunc("POST /api/v1/players/journey/wipe", handleJourneyWipe)
	mux.HandleFunc("POST /api/v1/players/contract/complete", handleCompleteContract)
	mux.HandleFunc("POST /api/v1/players/contracts/complete", handleCompleteContracts)
	mux.HandleFunc("POST /api/v1/players/contracts/reverse", handleReverseContracts)
	mux.HandleFunc("POST /api/v1/players/grant-job-skills", handleGrantJobSkills)
	mux.HandleFunc("POST /api/v1/players/reset-job-skills", handleResetJobSkills)
	mux.HandleFunc("POST /api/v1/players/set-starter-class", handleSetStarterClass)
	mux.HandleFunc("GET /api/v1/contracts", handleListContracts)
	mux.HandleFunc("POST /api/v1/players/delete-tutorials", handleDeleteTutorials)
	mux.HandleFunc("POST /api/v1/players/wipe-codex", handleWipeCodex)
	mux.HandleFunc("GET /api/v1/players/{id}/char-xp", handleGetCharXP)
	mux.HandleFunc("GET /api/v1/players/{id}/specs", handleGetPlayerSpecs)
	mux.HandleFunc("GET /api/v1/players/{id}/keystones", handleGetPlayerKeystones)
	mux.HandleFunc("POST /api/v1/players/grant-all-keystones", handleGrantAllKeystones)
	mux.HandleFunc("POST /api/v1/players/reset-all-keystones", handleResetAllKeystones)
	mux.HandleFunc("POST /api/v1/players/grant-max-spec", handleGrantMaxSpec)
	mux.HandleFunc("GET /api/v1/players/{id}/vehicles", handleGetPlayerVehicles)
	mux.HandleFunc("POST /api/v1/players/repair-item", handleRepairItem)
	mux.HandleFunc("POST /api/v1/players/repair-gear", handleRepairPlayerGear)
	mux.HandleFunc("POST /api/v1/players/repair-vehicle", handleRepairVehicle)
	mux.HandleFunc("POST /api/v1/players/refuel-vehicle", handleRefuelVehicle)
	mux.HandleFunc("GET /api/v1/players/partitions", handleGetPartitions)
	mux.HandleFunc("POST /api/v1/players/teleport", handleTeleportPlayer)
	mux.HandleFunc("POST /api/v1/players/teleport-coords", handleTeleportCoords)
	mux.HandleFunc("GET /api/v1/players/{id}/position", handleGetPlayerPosition)
	mux.HandleFunc("POST /api/v1/players/teleport-to-player", handleTeleportToPlayer)
	mux.HandleFunc("GET /api/v1/players/{id}/events", handleGetPlayerEvents)
	mux.HandleFunc("GET /api/v1/players/{id}/dungeons", handleGetPlayerDungeons)
	mux.HandleFunc("GET /api/v1/players/{id}/stats", handleGetPlayerStats)
	mux.HandleFunc("GET /api/v1/players/{id}/solaris-history", handleGetSolarisHistory)
	mux.HandleFunc("GET /api/v1/players/{id}/session-history", handleGetSessionHistory)
	mux.HandleFunc("GET /api/v1/players/{id}/stat-snapshot-history", handleGetStatSnapshotHistory)

	// ── database ──────────────────────────────────────────────────────────────
	mux.HandleFunc("GET /api/v1/database/tables", handleDBTables)
	mux.HandleFunc("GET /api/v1/database/describe", handleDBDescribe)
	mux.HandleFunc("GET /api/v1/database/sample", handleDBSample)
	mux.HandleFunc("GET /api/v1/database/search", handleDBSearch)
	mux.HandleFunc("POST /api/v1/database/sql", handleDBSQL)

	// ── locations (editable teleport/spawn points) ───────────────────────────
	mux.HandleFunc("GET /api/v1/locations", handleListLocations)
	mux.HandleFunc("POST /api/v1/locations", handleUpsertLocation)
	mux.HandleFunc("PUT /api/v1/locations", handleRenameLocation)
	mux.HandleFunc("DELETE /api/v1/locations", handleDeleteLocation)

	// ── live map ────────────────────────────────────────────────────────────────
	mux.HandleFunc("GET /api/v1/map/markers", handleGetMapMarkers)

	// ── logs ──────────────────────────────────────────────────────────────────
	mux.HandleFunc("GET /api/v1/logs/pods", handleLogPods)
	mux.HandleFunc("GET /api/v1/logs/stream", handleLogStream)
	mux.HandleFunc("GET /api/v1/logs/cheats", handleGetCheatLog)

	// ── notifications ────────────────────────────────────────────────────────
	mux.HandleFunc("POST /api/v1/notify", handleNotify)

	// ── server commands (RabbitMQ, fire-and-forget) ───────────────────────────
	mux.HandleFunc("POST /api/v1/players/kick", handleRMQKickPlayer)
	mux.HandleFunc("POST /api/v1/players/fill-water", handleRMQFillWater)
	mux.HandleFunc("POST /api/v1/players/set-skill-points", handleRMQSetSkillPoints)
	mux.HandleFunc("POST /api/v1/players/clean-inventory", handleRMQCleanInventory)
	mux.HandleFunc("POST /api/v1/players/reset-progression", handleRMQResetProgression)
	mux.HandleFunc("POST /api/v1/players/set-skill-module", handleRMQSetSkillModule)
	mux.HandleFunc("POST /api/v1/players/give-item-live", handleRMQGiveItem)
	mux.HandleFunc("POST /api/v1/players/cheat-script", handleRMQCheatScript)
	mux.HandleFunc("POST /api/v1/vehicles/spawn", handleRMQSpawnVehicle)
	mux.HandleFunc("POST /api/v1/broadcast", handleRMQBroadcast)
	mux.HandleFunc("POST /api/v1/broadcast/shutdown", handleRMQBroadcastShutdown)
	mux.HandleFunc("POST /api/v1/chat/whisper", handleRMQWhisper)
	mux.HandleFunc("GET /api/v1/players/{id}/player-ids", handlePlayerIDDebug)

	// ── storage ───────────────────────────────────────────────────────────────
	mux.HandleFunc("GET /api/v1/storage", handleListStorage)
	mux.HandleFunc("GET /api/v1/storage/{id}/items", handleGetStorageItems)
	mux.HandleFunc("POST /api/v1/storage/{id}/give-item", handleGiveItemToStorage)
	mux.HandleFunc("POST /api/v1/storage/{id}/give-items", handleGiveItemsToStorage)
	mux.HandleFunc("GET /api/v1/storage/{id}/owner-debug", handleStorageOwnerDebug)

	// ── blueprints ────────────────────────────────────────────────────────────
	mux.HandleFunc("GET /api/v1/blueprints", handleListBlueprints)
	mux.HandleFunc("GET /api/v1/blueprints/{id}/export", handleExportBlueprint)
	mux.HandleFunc("POST /api/v1/blueprints/import", handleImportBlueprint)

	// ── bases ─────────────────────────────────────────────────────────────────
	mux.HandleFunc("GET /api/v1/bases", handleListBases)
	mux.HandleFunc("GET /api/v1/bases/{id}/export", handleExportBase)

	// ── guilds ──────────────────────────────────────────────────────────────────
	mux.HandleFunc("GET /api/v1/guilds", handleListGuilds)
	mux.HandleFunc("GET /api/v1/guilds/{id}", handleGetGuild)
	mux.HandleFunc("PATCH /api/v1/guilds/{id}", handleUpdateGuild)
	mux.HandleFunc("PUT /api/v1/guilds/{id}/members/{pid}/role", handleSetGuildMemberRole)

	// ── landsraad (read-only) ─────────────────────────────────────────────────
	mux.HandleFunc("GET /api/v1/landsraad", handleGetLandsraad)

	// ── static data files (Go-first, CDN fallback on the frontend) ──────────
	mux.HandleFunc("GET /api/v1/data/{file}", handleGetDataFile)

	// ── market board ─────────────────────────────────────────────────────────
	mux.HandleFunc("GET /api/v1/market/items", handleMarketItems)
	mux.HandleFunc("GET /api/v1/market/listings", handleMarketListings)
	mux.HandleFunc("GET /api/v1/market/sales", handleMarketSales)
	mux.HandleFunc("GET /api/v1/market/stats", handleMarketStats)
	mux.HandleFunc("GET /api/v1/market/categories", handleMarketCategories)
	mux.HandleFunc("GET /api/v1/market/catalog", handleMarketCatalog)

	// ── market bot control ────────────────────────────────────────────────────
	mux.HandleFunc("GET /api/v1/market-bot/status", handleMarketBotStatus)
	mux.HandleFunc("GET /api/v1/market-bot/config", handleMarketBotConfig)
	mux.HandleFunc("PUT /api/v1/market-bot/config", handleMarketBotConfig)
	mux.HandleFunc("POST /api/v1/market-bot/exec", handleMarketBotExec)
	mux.HandleFunc("POST /api/v1/market-bot/cleanup", handleMarketBotCleanup)
	mux.HandleFunc("GET /api/v1/market-bot/logs-ready", handleMarketBotLogsReady)
	mux.HandleFunc("GET /api/v1/market-bot/logs", handleMarketBotLogs)

	// ── welcome package ───────────────────────────────────────────────────────
	mux.HandleFunc("GET /api/v1/welcome-package/config", handleGetWelcomeConfig)
	mux.HandleFunc("PUT /api/v1/welcome-package/config", handlePutWelcomeConfig)
	mux.HandleFunc("GET /api/v1/welcome-package/grants", handleGetWelcomeGrants)
	mux.HandleFunc("POST /api/v1/welcome-package/retry", handleRetryWelcomeGrant)
	mux.HandleFunc("POST /api/v1/welcome-package/revoke", handleRevokeWelcomeGrant)
	mux.HandleFunc("POST /api/v1/welcome-package/run", handleRunWelcomePackage)

	// ── give-items packs (operator-configurable pack library) ─────────────────
	mux.HandleFunc("GET /api/v1/give-packs/config", handleGetGivePacksConfig)
	mux.HandleFunc("PUT /api/v1/give-packs/config", handlePutGivePacksConfig)

	// ── swagger UI ────────────────────────────────────────────────────────────
	mux.Handle("/swagger/", httpSwagger.WrapHandler)

	// ── director reverse proxy (universal, opt-in) ──────────────────────────
	if loadedConfig.DirectorURL != "" {
		if target, err := url.Parse(loadedConfig.DirectorURL); err == nil {
			mux.HandleFunc("/director/", newDirectorProxy(target, dialThroughExecutor))
			log.Printf("Proxying /director/ → %s", loadedConfig.DirectorURL)
		}
	}

	// SPA frontend: prefer the embedded FS (release builds with -tags=embed),
	// then fall back to a local dist directory for dev/AMP deployments.
	if fsys := embeddedSPAFS(); fsys != nil {
		log.Println("Serving frontend from embedded assets")
		mux.Handle("/", spaHandlerFS(fsys))
	} else {
		for _, dir := range []string{"./dist", "./web/dist"} {
			if info, err := os.Stat(dir); err == nil && info.IsDir() {
				log.Printf("Serving frontend from %s", dir)
				mux.Handle("/", spaHandler(dir))
				break
			}
		}
	}

	srv := &http.Server{
		Addr:              addr,
		Handler:           corsMiddleware(mux),
		ReadHeaderTimeout: 5 * time.Second,
		ReadTimeout:       15 * time.Second,
		WriteTimeout:      10 * time.Minute, // backup/restore/download can take several minutes
		IdleTimeout:       60 * time.Second,
	}
	log.Printf("dune-admin listening on %s", addr)
	log.Fatal(srv.ListenAndServe())
}

// spaHandler serves static files from distDir, falling back to index.html
// for any path that does not match a real file (client-side routing).
func spaHandler(distDir string) http.Handler {
	fileServer := http.FileServer(http.Dir(distDir))
	cleanDist := filepath.Clean(distDir)
	sep := string(filepath.Separator)
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		p := filepath.Join(cleanDist, filepath.FromSlash(r.URL.Path))
		if p != cleanDist && !strings.HasPrefix(p, cleanDist+sep) {
			http.NotFound(w, r)
			return
		}
		if _, err := os.Stat(p); err == nil { // #nosec G703 -- path validated against cleanDist prefix above
			fileServer.ServeHTTP(w, r)
			return
		}
		http.ServeFile(w, r, filepath.Join(cleanDist, "index.html"))
	})
}

// spaHandlerFS serves an embedded http.FileSystem as a SPA, falling back to
// index.html for any path that does not map to a real file.
//
// Note: we open index.html directly instead of routing through http.FileServer
// because FileServer always 301-redirects "/index.html" → "/" which creates an
// infinite redirect loop (ERR_TOO_MANY_REDIRECTS) in browsers.
func spaHandlerFS(fsys http.FileSystem) http.Handler {
	fileServer := http.FileServer(fsys)
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/" && isRegularFile(fsys, r.URL.Path) {
			fileServer.ServeHTTP(w, r)
			return
		}
		f, err := fsys.Open("/index.html")
		if err != nil {
			http.NotFound(w, r)
			return
		}
		defer func() { _ = f.Close() }()
		fi, err := f.Stat()
		if err != nil {
			http.NotFound(w, r)
			return
		}
		http.ServeContent(w, r, "index.html", fi.ModTime(), f)
	})
}

func isRegularFile(fsys http.FileSystem, path string) bool {
	f, err := fsys.Open(path)
	if err != nil {
		return false
	}
	defer func() { _ = f.Close() }()
	fi, err := f.Stat()
	return err == nil && !fi.IsDir()
}

// ── JSON helpers ──────────────────────────────────────────────────────────────

func jsonOK(w http.ResponseWriter, v any) {
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(v)
}

func jsonErr(w http.ResponseWriter, err error, code int) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	_ = json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
}

func decode(r *http.Request, v any) error {
	return json.NewDecoder(r.Body).Decode(v)
}

// handleStatus returns connection state and provider info.
//
// @Summary Return connection state and build info
// @Tags status
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/status [get]
func handleStatus(w http.ResponseWriter, r *http.Request) {
	executorType := "none"
	controlName := "none"
	if globalExecutor != nil {
		executorType = globalExecutor.Type()
	}
	if globalControl != nil {
		controlName = globalControl.Name()
	}
	jsonOK(w, map[string]any{
		"executor":      executorType,
		"control":       controlName,
		"ssh_connected": globalSSH != nil,
		"db_connected":  globalDB != nil,
		"pod_ns":        globalPodNS,
		"pod_ip":        globalPodIP,
		"ssh_host":      sshHost,
		"db_host":       dbHost,
		"version":       AppVersion,
		"commit":        GitCommit,
		"build_time":    BuildTime,
		"director_url":  loadedConfig.DirectorURL,
		"listen_addr":   loadedConfig.ListenAddr,
	})
}

// handleReconnect tears down and re-establishes all connections.
//
// @Summary Tear down and re-establish all backend connections
// @Tags status
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Failure 500 {object} map[string]string
// @Router /api/v1/reconnect [post]
func handleReconnect(w http.ResponseWriter, r *http.Request) {
	if globalDB != nil {
		globalDB.Close()
		globalDB = nil
	}
	if globalExecutor != nil {
		globalExecutor.Close()
		globalExecutor = nil
	}
	globalSSH = nil
	globalControl = nil

	if err := connectAll(); err != nil {
		jsonErr(w, err, 500)
		return
	}
	handleStatus(w, r)
}
