package period

import (
	"errors"
	"net/http"
	"net/url"

	"github.com/gin-gonic/gin"
	metricsutils "github.com/yusing/godoxy/internal/metrics/utils"
	apitypes "github.com/yusing/goutils/apitypes"
	"github.com/yusing/goutils/http/httpheaders"
	"github.com/yusing/goutils/http/websocket"
)

type ResponseType[AggregateT any] struct {
	Total int        `json:"total"`
	Data  AggregateT `json:"data"`
}

// ServeHTTP serves the data for the given period.
//
// If the period is not specified, it serves the last result.
//
// If the period is specified, it serves the data for the given period.
//
// If the period is invalid, it returns a 400 error.
//
// If the data is not found, it returns a 204 error.
//
// If the request is a websocket request, it serves the data for the given period for every interval.
func (p *Poller[T, AggregateT]) ServeHTTP(c *gin.Context) {
	period := Filter(c.Query("period"))
	query := c.Request.URL.Query()

	if httpheaders.IsWebsocket(c.Request.Header) {
		interval := metricsutils.QueryDuration(query, "interval", 0)

		if interval < PollInterval {
			interval = PollInterval
		}
		websocket.PeriodicWrite(c, interval, func() (any, error) {
			return p.GetRespData(period, query)
		})
	} else {
		data, err := p.GetRespData(period, query)
		if err != nil {
			c.JSON(http.StatusBadRequest, apitypes.Error("bad request", err))
			return
		}
		if data == nil {
			c.JSON(http.StatusNoContent, apitypes.Error("no data"))
			return
		}
		c.JSON(http.StatusOK, data)
	}
}

// GetRespData returns the aggregated data for the given period and query.
//
// When period is specified:
//
//	It returns a map with the total and the data.
//	It returns an error if the period or query is invalid.
//
// When period is not specified:
//
//	It returns the last result.
//	It returns nil if no last result is found.
func (p *Poller[T, AggregateT]) GetRespData(period Filter, query url.Values) (any, error) {
	if period == "" {
		return p.GetLastResult(), nil
	}
	rangeData, ok := p.Get(period)
	if !ok {
		return nil, errors.New("invalid period")
	}
	total, aggregated := p.aggregate(rangeData, query)
	if total == -1 {
		return nil, errors.New("bad request")
	}
	return map[string]any{
		"total": total,
		"data":  aggregated,
	}, nil
}
