package api

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/alertstore/pkg/preview"
	"github.com/tidbcloud/alertstore/pkg/subscription"
)

// The two spans a form shows without asking: the week that says whether a
// filter matches at all, and the day that says whether it is matching now. Go
// duration spelling, as the mute rule's own duration field already uses.
var defaultPreviewWindows = []preview.Window{
	{Label: "168h", Span: 168 * time.Hour},
	{Label: "24h", Span: 24 * time.Hour},
}

// Takes a bare filter rather than the id of a saved one, so it answers for a
// rule nobody has stored yet — the only moment the answer changes anything.
type previewFilterRequest struct {
	// A subscription mode or preview.ModeMute. Required and with no default:
	// it decides which history the filter is run over, and a guess would
	// produce a confident count of the wrong thing.
	Mode    string          `json:"mode" binding:"required"`
	Filter  json.RawMessage `json:"filter" binding:"required" swaggertype:"object"`
	Windows []string        `json:"windows"`
}

// @Summary	Count what a subscription or mute filter would have matched
// @Id			previewFilter
// @Tags		Preview
// @Accept		json
// @Produce	json
// @Param		body	body		previewFilterRequest	true	"Mode, filter and windows"
// @Success	200		{object}	preview.Result
// @Failure	400		{object}	ErrorResponse	"Invalid mode, filter or window"
// @Failure	500		{object}	ErrorResponse
// @Router		/filter-preview [post]
func (s *Server) previewFilter(c *gin.Context) {
	var req previewFilterRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, errorBody(err))
		return
	}

	// Validated exactly as saving would be, so a filter that previews is one
	// that stores.
	if req.Mode == preview.ModeMute {
		if err := subscription.ValidateMuteFilter(req.Filter); err != nil {
			c.JSON(http.StatusBadRequest, errorBody(err))
			return
		}
	} else {
		if err := subscription.ValidateMode(req.Mode); err != nil {
			c.JSON(http.StatusBadRequest, errorBody(err))
			return
		}
		if err := subscription.ValidateFilter(req.Mode, req.Filter); err != nil {
			c.JSON(http.StatusBadRequest, errorBody(err))
			return
		}
	}

	var filter subscription.SubscriptionFilter
	if err := json.Unmarshal(req.Filter, &filter); err != nil {
		c.JSON(http.StatusBadRequest, errorBody(err))
		return
	}

	windows, err := parsePreviewWindows(req.Windows)
	if err != nil {
		c.JSON(http.StatusBadRequest, errorBody(err))
		return
	}

	result, err := s.previewStore.Run(c.Request.Context(), preview.Request{
		Mode:    req.Mode,
		Filter:  filter,
		Windows: windows,
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, errorBody(err))
		return
	}
	c.JSON(http.StatusOK, result)
}

func parsePreviewWindows(raw []string) ([]preview.Window, error) {
	if len(raw) == 0 {
		return defaultPreviewWindows, nil
	}
	windows := make([]preview.Window, 0, len(raw))
	for _, spec := range raw {
		d, err := time.ParseDuration(spec)
		if err != nil {
			return nil, fmt.Errorf("invalid window %q: %w", spec, err)
		}
		// The caller's own spelling, echoed back: the UI asked for "168h" and
		// should not have to recognize "168h0m0s" as its answer.
		windows = append(windows, preview.Window{Label: spec, Span: d})
	}
	return windows, nil
}
