package handlers

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"net/http"

	"golang.org/x/crypto/bcrypt"
	"xorm.io/xorm"

	"github.com/forgeo/forgebucket/internal/api/middleware"
	"github.com/forgeo/forgebucket/internal/models"
)

type RunnerHandler struct{ db *xorm.Engine }

func NewRunnerHandler(db *xorm.Engine) *RunnerHandler { return &RunnerHandler{db: db} }

// List returns all registered runners. Admin-only.
func (h *RunnerHandler) List(w http.ResponseWriter, r *http.Request) {
	if !isAdmin(r) {
		jsonError(w, "admin access required", http.StatusForbidden)
		return
	}
	var runners []models.Runner
	if err := h.db.Find(&runners); err != nil {
		jsonError(w, "could not list runners", http.StatusInternalServerError)
		return
	}
	if runners == nil {
		runners = []models.Runner{}
	}
	jsonOK(w, runners)
}

// Register creates a new runner record and returns the plaintext registration token
// (shown once; the server stores only the bcrypt hash).
func (h *RunnerHandler) Register(w http.ResponseWriter, r *http.Request) {
	if !isAdmin(r) {
		jsonError(w, "admin access required", http.StatusForbidden)
		return
	}

	var body struct {
		Name   string   `json:"name"`
		Labels []string `json:"labels"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		jsonError(w, "invalid request body", http.StatusBadRequest)
		return
	}
	if body.Name == "" {
		jsonError(w, "name is required", http.StatusBadRequest)
		return
	}

	raw := make([]byte, 32)
	if _, err := rand.Read(raw); err != nil {
		jsonError(w, "could not generate token", http.StatusInternalServerError)
		return
	}
	token := base64.RawURLEncoding.EncodeToString(raw)

	hash, err := bcrypt.GenerateFromPassword([]byte(token), bcrypt.DefaultCost)
	if err != nil {
		jsonError(w, "could not hash token", http.StatusInternalServerError)
		return
	}

	labelsJSON, _ := json.Marshal(body.Labels)
	runner := &models.Runner{
		Name:      body.Name,
		Labels:    string(labelsJSON),
		Status:    "idle",
		TokenHash: string(hash),
	}
	if _, err := h.db.Insert(runner); err != nil {
		jsonError(w, "runner name already taken", http.StatusConflict)
		return
	}

	w.WriteHeader(http.StatusCreated)
	jsonOK(w, map[string]any{
		"id":    runner.ID,
		"name":  runner.Name,
		"token": token, // shown once — store it securely
	})
}

func isAdmin(r *http.Request) bool {
	v, _ := r.Context().Value(middleware.ContextKeyIsAdmin).(bool)
	return v
}
