// repo_maintenance.go — REST API for /api/hosts/{id}/repo-maintenance.
//
// Cadence rows for the three repo-wide verbs (forget / prune / check).
// Edits do NOT bump host_schedule_version: the server-side maintenance
// ticker drives execution (P2R-06), not the agent's local cron.
package http

import (
	"encoding/json"
	"errors"
	stdhttp "net/http"

	"github.com/go-chi/chi/v5"

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

type repoMaintenanceView struct {
	HostID         string `json:"host_id"`
	ForgetCron     string `json:"forget_cron"`
	ForgetEnabled  bool   `json:"forget_enabled"`
	PruneCron      string `json:"prune_cron"`
	PruneEnabled   bool   `json:"prune_enabled"`
	CheckCron      string `json:"check_cron"`
	CheckEnabled   bool   `json:"check_enabled"`
	CheckSubsetPct int    `json:"check_subset_pct"`
}

func toRepoMaintenanceView(m store.HostRepoMaintenance) repoMaintenanceView {
	return repoMaintenanceView{
		HostID:         m.HostID,
		ForgetCron:     m.ForgetCron,
		ForgetEnabled:  m.ForgetEnabled,
		PruneCron:      m.PruneCron,
		PruneEnabled:   m.PruneEnabled,
		CheckCron:      m.CheckCron,
		CheckEnabled:   m.CheckEnabled,
		CheckSubsetPct: m.CheckSubsetPct,
	}
}

func (s *Server) handleGetRepoMaintenance(w stdhttp.ResponseWriter, r *stdhttp.Request) {
	if !s.authedUser(r) {
		writeJSONError(w, stdhttp.StatusUnauthorized, "unauthorised", "")
		return
	}
	hostID := chi.URLParam(r, "id")
	if _, err := s.deps.Store.GetHost(r.Context(), hostID); err != nil {
		writeJSONError(w, stdhttp.StatusNotFound, "host_not_found", "")
		return
	}
	m, err := s.deps.Store.GetRepoMaintenance(r.Context(), hostID)
	if err != nil {
		if errors.Is(err, store.ErrNotFound) {
			// Self-heal: seed and return the defaults so the UI never
			// has to handle a 404 here. Hosts enrolled before the
			// migration may legitimately be missing the row.
			if seedErr := s.deps.Store.CreateDefaultRepoMaintenance(r.Context(), hostID); seedErr != nil {
				writeJSONError(w, stdhttp.StatusInternalServerError, "internal", "")
				return
			}
			m, err = s.deps.Store.GetRepoMaintenance(r.Context(), hostID)
			if err != nil {
				writeJSONError(w, stdhttp.StatusInternalServerError, "internal", "")
				return
			}
		} else {
			writeJSONError(w, stdhttp.StatusInternalServerError, "internal", "")
			return
		}
	}
	writeJSON(w, stdhttp.StatusOK, toRepoMaintenanceView(*m))
}

type repoMaintenanceWriteRequest struct {
	ForgetCron     string `json:"forget_cron"`
	ForgetEnabled  bool   `json:"forget_enabled"`
	PruneCron      string `json:"prune_cron"`
	PruneEnabled   bool   `json:"prune_enabled"`
	CheckCron      string `json:"check_cron"`
	CheckEnabled   bool   `json:"check_enabled"`
	CheckSubsetPct int    `json:"check_subset_pct"`
}

func (s *Server) handleUpdateRepoMaintenance(w stdhttp.ResponseWriter, r *stdhttp.Request) {
	if !s.authedUser(r) {
		writeJSONError(w, stdhttp.StatusUnauthorized, "unauthorised", "")
		return
	}
	hostID := chi.URLParam(r, "id")
	if _, err := s.deps.Store.GetHost(r.Context(), hostID); err != nil {
		writeJSONError(w, stdhttp.StatusNotFound, "host_not_found", "")
		return
	}
	var req repoMaintenanceWriteRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSONError(w, stdhttp.StatusBadRequest, "invalid_json", err.Error())
		return
	}
	for label, expr := range map[string]string{
		"forget_cron": req.ForgetCron,
		"prune_cron":  req.PruneCron,
		"check_cron":  req.CheckCron,
	} {
		if expr == "" {
			writeJSONError(w, stdhttp.StatusBadRequest, "missing_field", label+" required")
			return
		}
		if _, err := cronParser.Parse(expr); err != nil {
			writeJSONError(w, stdhttp.StatusBadRequest, "invalid_cron", label+": "+err.Error())
			return
		}
	}
	if req.CheckSubsetPct < 0 || req.CheckSubsetPct > 100 {
		writeJSONError(w, stdhttp.StatusBadRequest, "invalid_value",
			"check_subset_pct must be 0..100")
		return
	}
	// Ensure the row exists (older hosts may pre-date the auto-seed).
	if err := s.deps.Store.CreateDefaultRepoMaintenance(r.Context(), hostID); err != nil {
		writeJSONError(w, stdhttp.StatusInternalServerError, "internal", "")
		return
	}
	m := store.HostRepoMaintenance{
		HostID:         hostID,
		ForgetCron:     req.ForgetCron,
		ForgetEnabled:  req.ForgetEnabled,
		PruneCron:      req.PruneCron,
		PruneEnabled:   req.PruneEnabled,
		CheckCron:      req.CheckCron,
		CheckEnabled:   req.CheckEnabled,
		CheckSubsetPct: req.CheckSubsetPct,
	}
	if err := s.deps.Store.UpdateRepoMaintenance(r.Context(), &m); err != nil {
		writeJSONError(w, stdhttp.StatusInternalServerError, "internal", err.Error())
		return
	}
	out, _ := s.deps.Store.GetRepoMaintenance(r.Context(), hostID)
	if out != nil {
		writeJSON(w, stdhttp.StatusOK, toRepoMaintenanceView(*out))
		return
	}
	writeJSON(w, stdhttp.StatusOK, toRepoMaintenanceView(m))
}
