package api

import (
	"errors"
	"fmt"
	"net/http"
	"os"
	"strconv"

	"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/auth"
	"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/models"
	"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/services"
	"github.com/gin-gonic/gin"
)

func actorFromCtx(c *gin.Context) string {
	if sess := auth.GetSessionFromContext(c); sess != nil && sess.Email != "" {
		return sess.Email
	}
	return "admin"
}

func RegisterRoutes(r *gin.Engine) {
	r.GET("/healthz", handleHealthz)
	r.GET("/readyz", handleReadyz)

	r.GET("/install", handleInstallScript)
	r.GET("/install.ps1", handleInstallScriptWindows)
	r.GET("/update", handleUpdateScript)
	r.GET("/update.ps1", handleUpdateScriptWindows)

	r.GET("/api/secrets/:group/values", secretsReadAuth(), esoGetGroup)

	r.GET("/auth/bootstrap-status", auth.HandleBootstrapStatus)
	r.POST("/auth/bootstrap", auth.HandleBootstrap)
	r.POST("/auth/login", auth.HandleLocalLogin)
	r.POST("/auth/logout", auth.HandleLogout)
	r.GET("/auth/me", auth.HandleMe)
	r.GET("/auth/oidc/:providerId/start", auth.HandleSSOStart)
	r.GET("/auth/oidc/:providerId/callback", auth.HandleSSOCallback)
	r.GET("/auth/providers", auth.HandleListPublicProviders)

	apiGroup := r.Group("/api")
	apiGroup.Use(auth.Middleware())
	// Deny by default: every non-GET route under /api is gated unless it is on
	// the exemption list in licence.go. A route added later is covered because
	// of where it is mounted, not because someone remembered.
	apiGroup.Use(RequireActiveLicense())
	{
		apiGroup.GET("/license", getLicence)
		apiGroup.POST("/license", auth.RequireRole("owner"), postLicence)

		apiGroup.GET("/servers", listServers)
		// Static segment, registered alongside /servers/:id exactly as
		// /servers/new already is — gin resolves statics ahead of wildcards.
		apiGroup.GET("/servers/tags", listKnownTags)
		apiGroup.POST("/servers", createServer)
		apiGroup.GET("/servers/new", newServer)
		apiGroup.POST("/servers/new", newServer)
		apiGroup.GET("/servers/:id", getServer)
		apiGroup.DELETE("/servers/:id", deleteServer)
		apiGroup.POST("/servers/:id/generate-key", generateKey)
		apiGroup.POST("/servers/:id/update-agent", updateAgent)
		apiGroup.POST("/servers/:id/apply-updates", applyUpdates)
		apiGroup.PUT("/servers/:id/tags", putServerTags)

		apiGroup.GET("/agent/latest-version", getLatestAgentVersion)

		apiGroup.GET("/audit", listAuditEvents)

		settings := apiGroup.Group("/settings")
		settings.Use(auth.RequireRole("owner", "admin"))
		{
			settings.GET("", getSettings)
			settings.PUT("", saveSettings)
			settings.POST("/secrets-token", rotateSecretsToken)
		}

		apiGroup.GET("/secrets", listSecretGroups)
		apiGroup.POST("/secrets", createSecretGroup)
		apiGroup.GET("/secrets/:group", getSecretGroup)
		apiGroup.PUT("/secrets/:group", putSecretGroup)
		apiGroup.POST("/secrets/:group/reveal", revealSecret)
		apiGroup.DELETE("/secrets/:group", deleteSecretGroup)
		apiGroup.DELETE("/secrets/:group/:key", deleteSecretKey)

		apiGroup.GET("/keys", listKeys)
		apiGroup.POST("/keys", createKey)
		apiGroup.GET("/keys/:id", getKey)
		apiGroup.GET("/keys/:id/private-key", getPrivateKey)
		apiGroup.DELETE("/keys/:id", deleteKey)
		apiGroup.POST("/keys/:id/assign", assignKey)
		apiGroup.DELETE("/keys/:id/assign/:serverId", revokeAssignment)

		apiGroup.POST("/console/connect", RequireFeature("console"), consoleConnect)
		apiGroup.GET("/console/tunnel", RequireFeature("console"), consoleTunnel)

		registerWorkflowRoutes(apiGroup)
		registerMonitorRoutes(apiGroup)
		registerChannelRoutes(apiGroup)

		instance := apiGroup.Group("/instance")
		instance.Use(auth.RequireRole("owner", "admin"))
		{
			instance.GET("/users", listInstanceUsers)
			instance.POST("/users", createInstanceUser)
			instance.PUT("/users/:id/role", updateInstanceUserRole)
			instance.DELETE("/users/:id", deleteInstanceUser)
		}

		providers := apiGroup.Group("/auth/providers")
		providers.Use(auth.RequireRole("owner", "admin"), RequireFeature("oidc"))
		{
			providers.GET("", listAuthProviders)
			providers.POST("", createAuthProvider)
			providers.PUT("/:id", updateAuthProvider)
			providers.DELETE("/:id", deleteAuthProvider)
			providers.POST("/:id/test", testAuthProvider)
			providers.POST("/:id/ack-notice", ackAuthProviderNotice)
		}
		apiGroup.GET("/auth/presets", auth.RequireRole("owner", "admin"), listAuthPresets)

		apiGroup.GET("/vulnerabilities", listVulnerabilities)
		apiGroup.GET("/vulnerabilities/summary", vulnerabilitySummary)
		apiGroup.POST("/vulnerabilities/rescan", auth.RequireRole("owner", "admin"), rescanVulnerabilities)
		apiGroup.POST("/vulnerabilities/:id/accept", auth.RequireRole("owner", "admin"), acceptFinding)
		apiGroup.DELETE("/vulnerabilities/:id/accept", auth.RequireRole("owner", "admin"), unacceptFinding)
		apiGroup.GET("/servers/:id/vulnerabilities", listServerVulnerabilities)
		apiGroup.GET("/servers/:id/packages", getServerPackages)
		apiGroup.GET("/packages/search", searchPackages)
		apiGroup.GET("/vuln-rules", listVulnRules)
		apiGroup.POST("/vuln-rules", auth.RequireRole("owner", "admin"), createVulnRule)
		apiGroup.PUT("/vuln-rules/:id", auth.RequireRole("owner", "admin"), updateVulnRule)
		apiGroup.DELETE("/vuln-rules/:id", auth.RequireRole("owner", "admin"), deleteVulnRule)

		// Control actions and log reads are owner|admin: container output is
		// arbitrary and cannot be masked, so a member who can see the fleet
		// still cannot read its logs.
		apiGroup.GET("/workloads", listWorkloads)
		apiGroup.GET("/servers/:id/workloads", getServerWorkloads)
		apiGroup.POST("/servers/:id/workloads/refresh", refreshServerWorkloads)
		apiGroup.POST("/servers/:id/workloads/:wid/action", auth.RequireRole("owner", "admin"), controlWorkload)
		apiGroup.GET("/servers/:id/workloads/:wid/logs", auth.RequireRole("owner", "admin"), getWorkloadLogs)
	}
}

func listServers(c *gin.Context) {
	sel, err := services.ParseTagFilters(c.QueryArray("tag"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	servers, err := services.ListServersFiltered(auth.InstanceID(c), sel)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, servers)
}

func listKnownTags(c *gin.Context) {
	tags, err := services.KnownTags(auth.InstanceID(c))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, tags)
}

func putServerTags(c *gin.Context) {
	var body struct {
		Tags map[string]string `json:"tags"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body"})
		return
	}

	instanceID := auth.InstanceID(c)
	serverID := c.Param("id")

	before, err := services.GetServer(instanceID, serverID)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "server not found"})
		return
	}

	if err := services.SetServerTags(instanceID, serverID, body.Tags); err != nil {
		if errors.Is(err, services.ErrInvalidTag) {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	services.LogEvent(instanceID, "server.tags_updated", actorFromCtx(c), serverID, "",
		fmt.Sprintf("tags %v -> %v", before.Tags, body.Tags))
	c.JSON(http.StatusOK, gin.H{"tags": body.Tags})
}

func createServer(c *gin.Context) {
	s, token, err := services.CreateServer(auth.InstanceID(c))
	if err != nil {
		if limitStatus(c, err) {
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusCreated, gin.H{
		"server":    s,
		"token":     token,
		"server_id": s.ServerID,
	})
}

func newServer(c *gin.Context) {
	s, token, err := services.CreateServer(auth.InstanceID(c))
	if err != nil {
		if limitStatus(c, err) {
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "server.created", actorFromCtx(c), s.ServerID, "", "pre-registration token issued")

	host := publicHostFromRequest(c)

	installCmd := fmt.Sprintf(
		`curl -fsSL "%s/install?server_id=%s&token=%s" | bash`,
		host, s.ServerID, token,
	)

	installCmdPS := fmt.Sprintf(
		`irm "%s/install.ps1?server_id=%s&token=%s" | iex`,
		host, s.ServerID, token,
	)

	c.JSON(http.StatusOK, gin.H{
		"server_id":          s.ServerID,
		"pre_reg_token":      token,
		"install_command":    installCmd,
		"install_command_ps": installCmdPS,
	})
}

func getServer(c *gin.Context) {
	id := c.Param("id")
	s, err := services.GetServer(auth.InstanceID(c), id)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "server not found"})
		return
	}

	assignments, _ := services.GetAssignmentsWithKeysForServer(auth.InstanceID(c), id)

	type serverResponse struct {
		*models.Server
		Keys interface{} `json:"keys"`
	}
	c.JSON(http.StatusOK, serverResponse{
		Server: s,
		Keys:   assignments,
	})
}

func deleteServer(c *gin.Context) {
	id := c.Param("id")
	s, _ := services.GetServer(auth.InstanceID(c), id)
	if err := services.DeleteServer(auth.InstanceID(c), id); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	hostname := id
	if s != nil {
		hostname = s.Hostname
	}
	services.LogEvent(auth.InstanceID(c), "server.deleted", actorFromCtx(c), id, "", fmt.Sprintf("server %s deleted", hostname))
	c.JSON(http.StatusOK, gin.H{"deleted": true})
}

func generateKey(c *gin.Context) {
	id := c.Param("id")

	var body struct {
		Label      string `json:"label"`
		KeyType    string `json:"key_type"`
		KeySize    int    `json:"key_size"`
		Passphrase string `json:"passphrase"`
		Comment    string `json:"comment"`
	}
	_ = c.ShouldBindJSON(&body)
	if body.Label == "" {
		body.Label = "generated"
	}

	s, err := services.GetServer(auth.InstanceID(c), id)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "server not found"})
		return
	}

	cmdID, err := services.DispatchGenerateKey(s.ServerID, services.KeyGenParams{
		Label:      body.Label,
		KeyType:    body.KeyType,
		KeySize:    body.KeySize,
		Passphrase: body.Passphrase,
		Comment:    body.Comment,
	})
	if err != nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()})
		return
	}

	services.LogEvent(auth.InstanceID(c), "key.generation_dispatched", actorFromCtx(c), s.ServerID, "", fmt.Sprintf("key generation dispatched (label=%s type=%s)", body.Label, body.KeyType))
	c.JSON(http.StatusAccepted, gin.H{
		"message":    "key generation command sent to agent",
		"command_id": cmdID,
		"server_id":  s.ServerID,
	})
}

func listKeys(c *gin.Context) {
	keys, err := services.ListKeys(auth.InstanceID(c))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, keys)
}

func createKey(c *gin.Context) {
	var body struct {
		Label      string `json:"label" binding:"required"`
		PublicKey  string `json:"public_key" binding:"required"`
		PrivateKey string `json:"private_key"`
		Passphrase string `json:"passphrase"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	key, err := services.CreateKey(auth.InstanceID(c), body.Label, body.PublicKey, "uploaded", "", body.PrivateKey, body.Passphrase)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "key.uploaded", actorFromCtx(c), "", key.KeyID, fmt.Sprintf("key '%s' uploaded", key.Label))
	c.JSON(http.StatusCreated, key)
}

func getPrivateKey(c *gin.Context) {
	id := c.Param("id")
	plaintext, err := services.GetPrivateKey(auth.InstanceID(c), id)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"private_key": plaintext})
}

func getKey(c *gin.Context) {
	id := c.Param("id")
	key, err := services.GetKey(auth.InstanceID(c), id)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "key not found"})
		return
	}

	assignments, _ := services.GetAssignmentsWithServers(auth.InstanceID(c), id)

	type keyResponse struct {
		*models.Key
		Assignments any `json:"assignments"`
	}
	c.JSON(http.StatusOK, keyResponse{
		Key:         key,
		Assignments: assignments,
	})
}

func deleteKey(c *gin.Context) {
	id := c.Param("id")
	k, _ := services.GetKey(auth.InstanceID(c), id)
	if err := services.DeleteKey(auth.InstanceID(c), id); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	label := id
	if k != nil {
		label = k.Label
	}
	services.LogEvent(auth.InstanceID(c), "key.deleted", actorFromCtx(c), "", id, fmt.Sprintf("key '%s' deleted", label))
	c.JSON(http.StatusOK, gin.H{"deleted": true})
}

func assignKey(c *gin.Context) {
	keyID := c.Param("id")
	var body struct {
		ServerID string `json:"server_id" binding:"required"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	a, err := services.AssignKey(auth.InstanceID(c), keyID, body.ServerID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "key.assigned", actorFromCtx(c), body.ServerID, keyID, fmt.Sprintf("key %s assigned to server %s", keyID, body.ServerID))
	c.JSON(http.StatusCreated, a)
}

func revokeAssignment(c *gin.Context) {
	keyID := c.Param("id")
	serverID := c.Param("serverId")

	if err := services.RevokeAssignment(auth.InstanceID(c), keyID, serverID); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "key.revoked", actorFromCtx(c), serverID, keyID, fmt.Sprintf("key %s revoked from server %s", keyID, serverID))
	c.JSON(http.StatusOK, gin.H{"revoked": true})
}

func getLatestAgentVersion(c *gin.Context) {
	version, err := services.GetLatestAgentVersion()
	if err != nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"version": version})
}

func updateAgent(c *gin.Context) {
	id := c.Param("id")
	s, err := services.GetServer(auth.InstanceID(c), id)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "server not found"})
		return
	}

	version, err := services.DispatchUpdateAgent(s.ServerID)
	if err != nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "agent.update_dispatched", actorFromCtx(c), s.ServerID, "", fmt.Sprintf("agent update dispatched to %s (version %s)", s.Hostname, version))
	c.JSON(http.StatusAccepted, gin.H{
		"message": "update command sent to agent",
		"version": version,
	})
}

func applyUpdates(c *gin.Context) {
	id := c.Param("id")
	s, err := services.GetServer(auth.InstanceID(c), id)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "server not found"})
		return
	}

	if err := services.DispatchApplyUpdates(s.ServerID); err != nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "updates.applied", actorFromCtx(c), s.ServerID, "", fmt.Sprintf("package update command dispatched to %s", s.Hostname))
	c.JSON(http.StatusAccepted, gin.H{"message": "apply updates command sent to agent"})
}

func handleUpdateScript(c *gin.Context) {
	giteaHost := "gitea.hostxtra.co.uk"

	script := fmt.Sprintf(`#!/usr/bin/env bash
set -euo pipefail

GITEA_HOST="%s"

ARCH=$(uname -m)
case "$ARCH" in
  x86_64)  ARCH="amd64" ;;
  aarch64) ARCH="arm64" ;;
  *) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;;
esac

# Get latest agent release tag
LATEST=$(curl -fsSL "https://${GITEA_HOST}/api/v1/repos/mrhid6/vantage/releases?limit=10" \
  | grep -o '"tag_name":"agent/v[^"]*"' | head -1 | sed 's/"tag_name":"//;s/"//')

if [ -z "$LATEST" ]; then
  echo "Could not determine latest agent version" >&2
  exit 1
fi

VERSION="${LATEST#agent/}"
LATEST_ENCODED="${LATEST/\//%%2F}"
BINARY_URL="https://${GITEA_HOST}/mrhid6/vantage/releases/download/${LATEST_ENCODED}/vantage-agent-linux-${ARCH}"
CHECKSUM_URL="https://${GITEA_HOST}/mrhid6/vantage/releases/download/${LATEST_ENCODED}/checksums.txt"

echo "Updating vantage-agent to ${VERSION} (${ARCH})..."

curl -fsSL -o /tmp/vantage-agent "${BINARY_URL}"
curl -fsSL -o /tmp/checksums.txt "${CHECKSUM_URL}"

cd /tmp
EXPECTED=$(grep "vantage-agent-linux-${ARCH}" checksums.txt | awk '{print $1}')
ACTUAL=$(sha256sum vantage-agent | awk '{print $1}')
if [ "$EXPECTED" != "$ACTUAL" ]; then
  echo "Checksum mismatch!" >&2
  exit 1
fi

systemctl stop vantage-agent || true
install -m 0755 /tmp/vantage-agent /usr/local/bin/vantage-agent
systemctl start vantage-agent

echo "vantage-agent updated to ${VERSION} and restarted."
`, giteaHost)

	c.Header("Content-Type", "text/x-shellscript")
	c.String(http.StatusOK, script)
}

func listAuditEvents(c *gin.Context) {
	limit := int64(100)
	if l := c.Query("limit"); l != "" {
		if n, err := strconv.ParseInt(l, 10, 64); err == nil && n > 0 {
			limit = n
		}
	}
	events, err := services.ListAuditEvents(auth.InstanceID(c), limit)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, events)
}

func getSettings(c *gin.Context) {
	s, err := services.GetSettings(auth.InstanceID(c))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, s)
}

func saveSettings(c *gin.Context) {
	var body struct {
		Alerts                   models.AlertSettings `json:"alerts"`
		WorkflowLogRetentionDays *int                 `json:"workflow_log_retention_days"`
		LocalLoginEnabled        *bool                `json:"local_login_enabled"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if err := services.SaveSettings(auth.InstanceID(c), body.Alerts, body.WorkflowLogRetentionDays, body.LocalLoginEnabled); err != nil {
		if errors.Is(err, services.ErrLockout) {
			c.JSON(http.StatusConflict, gin.H{"error": err.Error(), "code": "local_login_required"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	services.LogEvent(auth.InstanceID(c), "settings.updated", actorFromCtx(c), "", "", "alert settings updated")
	c.JSON(http.StatusOK, gin.H{"saved": true})
}

func handleInstallScript(c *gin.Context) {
	serverID := c.Query("server_id")
	token := c.Query("token")

	giteaHost := os.Getenv("GITEA_HOST")
	if giteaHost == "" {
		giteaHost = "gitea.example.com"
	}
	grpcHost := os.Getenv("GRPC_HOST")

	script := fmt.Sprintf(`#!/usr/bin/env bash
set -euo pipefail

SERVER_ID="%s"
TOKEN="%s"
GITEA_HOST="%s"
GRPC_HOST="%s"
GRPC_HOST="${GRPC_HOST#https://}"
GRPC_HOST="${GRPC_HOST#http://}"

ARCH=$(uname -m)
case "$ARCH" in
  x86_64)  ARCH="amd64" ;;
  aarch64) ARCH="arm64" ;;
  *) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;;
esac

# Get latest agent release tag
LATEST=$(curl -fsSL "https://${GITEA_HOST}/api/v1/repos/mrhid6/vantage/releases?limit=10" \
  | grep -o '"tag_name":"agent/v[^"]*"' | head -1 | sed 's/"tag_name":"//;s/"//')

if [ -z "$LATEST" ]; then
  echo "Could not determine latest agent version" >&2
  exit 1
fi

VERSION="${LATEST#agent/}"
LATEST_ENCODED="${LATEST/\//%%2F}"
BINARY_URL="https://${GITEA_HOST}/mrhid6/vantage/releases/download/${LATEST_ENCODED}/vantage-agent-linux-${ARCH}"
CHECKSUM_URL="https://${GITEA_HOST}/mrhid6/vantage/releases/download/${LATEST_ENCODED}/checksums.txt"

echo "Installing vantage-agent ${VERSION} (${ARCH})..."

curl -fsSL -o /tmp/vantage-agent "${BINARY_URL}"
curl -fsSL -o /tmp/checksums.txt "${CHECKSUM_URL}"

cd /tmp
EXPECTED=$(grep "vantage-agent-linux-${ARCH}" checksums.txt | awk '{print $1}')
ACTUAL=$(sha256sum vantage-agent | awk '{print $1}')
if [ "$EXPECTED" != "$ACTUAL" ]; then
  echo "Checksum mismatch!" >&2
  exit 1
fi

install -m 0755 /tmp/vantage-agent /usr/local/bin/vantage-agent

mkdir -p /etc/vantage
chmod 0700 /etc/vantage

cat > /etc/vantage/config.yaml <<EOF
server_url: "${GRPC_HOST}"
server_id: "${SERVER_ID}"
pre_reg_token: "${TOKEN}"
agent_token: ""
poll_interval: 30s
tls: true
EOF
chmod 0600 /etc/vantage/config.yaml

cat > /etc/systemd/system/vantage-agent.service <<EOF
[Unit]
Description=Vantage Agent
After=network.target

[Service]
ExecStart=/usr/local/bin/vantage-agent
Restart=always
RestartSec=10
User=root

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now vantage-agent

echo "vantage-agent installed and started."
`, serverID, token, giteaHost, grpcHost)

	c.Header("Content-Type", "text/x-shellscript")
	c.String(http.StatusOK, script)
}
