package http

import (
	"fmt"
	stdhttp "net/http"
	"os"
	"path/filepath"
	"strings"

	"gitea.dcglab.co.uk/steve/restic-manager/internal/api"
)

// agent_assets.go serves the agent binary (one per OS/arch) and the
// install scripts. The binaries live under <DataDir>/agent-binaries/,
// laid down by the release pipeline (or copied by hand for now).
// The install scripts live in <DataDir>/install/ alongside the
// systemd unit.
//
// Both endpoints are intentionally unauthenticated: the install
// payload is unprivileged on its own — it's the one-time enrollment
// token that grants access. Anyone can pull the binary; only
// someone with a valid token can use it productively.
//
// P1-31: signed-binary verification is deferred. Today we serve
// whatever the operator dropped on disk. Future work bumps this to
// minisign/cosign signed bundles.

// installAssetsRoutes adds /agent/binary and /install/* to r.
func (s *Server) handleAgentBinary(w stdhttp.ResponseWriter, r *stdhttp.Request) {
	osTag := r.URL.Query().Get("os")
	archTag := r.URL.Query().Get("arch")
	if osTag == "" || archTag == "" {
		writeJSONError(w, stdhttp.StatusBadRequest, "missing_os_or_arch",
			"query params os= and arch= are required")
		return
	}
	if !validOS(osTag) || !validArch(archTag) {
		writeJSONError(w, stdhttp.StatusBadRequest, "unsupported_target",
			fmt.Sprintf("os=%q arch=%q not in {linux,windows} × {amd64,arm64}", osTag, archTag))
		return
	}

	ext := ""
	if osTag == "windows" {
		ext = ".exe"
	}
	name := fmt.Sprintf("restic-manager-agent-%s-%s%s", osTag, archTag, ext)
	path := filepath.Join(s.deps.Cfg.DataDir, "agent-binaries", name)
	if _, err := os.Stat(path); err != nil {
		writeJSONError(w, stdhttp.StatusNotFound, "binary_not_published",
			fmt.Sprintf("agent binary for %s/%s not published on this server", osTag, archTag))
		return
	}
	w.Header().Set("Content-Type", "application/octet-stream")
	w.Header().Set("Content-Disposition", `attachment; filename="`+name+`"`)
	stdhttp.ServeFile(w, r, path)
}

func (s *Server) handleInstallAsset(w stdhttp.ResponseWriter, r *stdhttp.Request) {
	// chi's TrimPrefix-like behaviour: r.URL.Path is "/install/<file>".
	rel := strings.TrimPrefix(r.URL.Path, "/install/")
	// Reject any path traversal — must be a flat filename.
	if rel == "" || strings.ContainsAny(rel, "/\\") {
		writeJSONError(w, stdhttp.StatusBadRequest, "bad_path", "")
		return
	}
	path := filepath.Join(s.deps.Cfg.DataDir, "install", rel)
	if _, err := os.Stat(path); err != nil {
		writeJSONError(w, stdhttp.StatusNotFound, "not_found", "")
		return
	}
	stdhttp.ServeFile(w, r, path)
}

func validOS(s string) bool {
	switch api.HostOS(s) {
	case api.OSLinux, api.OSWindows:
		return true
	}
	return false
}

func validArch(s string) bool {
	switch api.HostArch(s) {
	case api.ArchAmd64, api.ArchArm64:
		return true
	}
	return false
}
