package main

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"google.golang.org/protobuf/types/known/timestamppb"

	"github.com/dbledeez/panel/controller/internal/db"
	panelv1 "github.com/dbledeez/panel/proto/panel/v1"
)

// WI-14 "update available" detection. CHECK-ONLY: nothing here ever
// triggers an update or restart — it compares the installed Steam
// buildid (from the instance's appmanifest ACF) against the latest
// buildid for its branch (steamcmd app_info, cached 15 min agent-side)
// and stores the answer.

// updateCheckTimeout bounds the agent round-trip. A cold check spins a
// steamcmd sidecar (image pull possible on first-ever run), so this is
// generous; cached checks answer in milliseconds.
const updateCheckTimeout = 5 * time.Minute

func updateCheckJSON(r *db.UpdateCheckRow) map[string]any {
	return map[string]any{
		"instance_id":       r.InstanceID,
		"app_id":            r.AppID,
		"branch":            r.Branch,
		"installed_buildid": r.InstalledBuildID,
		"latest_buildid":    r.LatestBuildID,
		"update_available":  r.UpdateAvailable,
		"error":             r.Error,
		"checked_at":        r.CheckedAt,
	}
}

// getUpdateCheck returns the last stored check for an instance without
// contacting the agent. 200 {"checked": false} when never checked.
func (h *httpServer) getUpdateCheck(w http.ResponseWriter, r *http.Request) {
	id := r.PathValue("id")
	row, err := h.db.GetUpdateCheck(r.Context(), id)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "db", err.Error())
		return
	}
	if row == nil {
		writeJSON(w, http.StatusOK, map[string]any{"checked": false})
		return
	}
	out := updateCheckJSON(row)
	out["checked"] = true
	writeJSON(w, http.StatusOK, out)
}

// listUpdateChecks returns every stored check — the dashboard uses it to
// paint "Update available" badges on server cards in one request.
func (h *httpServer) listUpdateChecks(w http.ResponseWriter, r *http.Request) {
	rows, err := h.db.ListUpdateChecks(r.Context())
	if err != nil {
		writeError(w, http.StatusInternalServerError, "db", err.Error())
		return
	}
	out := make([]map[string]any, 0, len(rows))
	for i := range rows {
		out = append(out, updateCheckJSON(&rows[i]))
	}
	writeJSON(w, http.StatusOK, map[string]any{"checks": out})
}

// postUpdateCheck runs a live check via the instance's agent, stores the
// result, and returns it.
func (h *httpServer) postUpdateCheck(w http.ResponseWriter, r *http.Request) {
	id := r.PathValue("id")
	_, conn, err := h.resolveInstanceConn(r.Context(), id)
	if err != nil {
		writeError(w, http.StatusNotFound, "resolve", err.Error())
		return
	}
	// Provider selection mirrors updateInstance: for 7DTD the pinned
	// branch lives in config_values.provider_id; empty means "the
	// module's default provider" agent-side. `?provider=` overrides.
	providerID := r.URL.Query().Get("provider")
	if providerID == "" {
		if row, rerr := h.db.GetInstanceRow(r.Context(), id); rerr == nil && row.ConfigValues != nil {
			providerID = row.ConfigValues["provider_id"]
		}
	}
	refresh := r.URL.Query().Get("refresh") == "1"

	res, err := h.sendUpdateCheck(r.Context(), conn, id, providerID, refresh)
	if err != nil {
		writeError(w, http.StatusBadGateway, "check_failed", err.Error())
		return
	}
	row := db.UpdateCheckRow{
		InstanceID:       id,
		AppID:            res.AppId,
		Branch:           res.Branch,
		InstalledBuildID: res.InstalledBuildid,
		LatestBuildID:    res.LatestBuildid,
		UpdateAvailable:  res.UpdateAvailable,
		Error:            res.Error,
		CheckedAt:        time.Now(),
	}
	if err := h.db.UpsertUpdateCheck(r.Context(), row); err != nil {
		h.log.Warn("update check: persist failed", "instance_id", id, "err", err)
	}
	out := updateCheckJSON(&row)
	out["checked"] = true
	out["cached"] = res.Cached
	writeJSON(w, http.StatusOK, out)
}

// sendUpdateCheck forwards an UpdateCheckRequest and awaits the
// correlation-routed reply.
func (h *httpServer) sendUpdateCheck(ctx context.Context, conn *agentConn, instanceID, providerID string, refresh bool) (*panelv1.UpdateCheckResult, error) {
	corrID := newCorrelationID("update-check")
	env := &panelv1.ControllerEnvelope{
		CorrelationId: corrID, SentAt: timestamppb.Now(),
		Payload: &panelv1.ControllerEnvelope_UpdateCheck{UpdateCheck: &panelv1.UpdateCheckRequest{
			InstanceId: instanceID,
			ProviderId: providerID,
			Refresh:    refresh,
		}},
	}
	if err := conn.Send(env); err != nil {
		return nil, err
	}
	reply, err := h.pending.Await(ctx, corrID, updateCheckTimeout)
	if err != nil {
		return nil, err
	}
	res := reply.GetUpdateCheckResult()
	if res == nil {
		return nil, fmt.Errorf("agent reply missing update_check_result")
	}
	return res, nil
}
