package topru

import (
	"context"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/log"
	"go.uber.org/zap"

	apierr "github.com/pingcap-inc/clinic/pkg/api/error"
	"github.com/pingcap-inc/clinic/pkg/ngm/utils"
	"github.com/pingcap-inc/clinic/pkg/o11y"
)

const DefaultTimeout = time.Second * 180

func InitTopRuApi(g *gin.RouterGroup, s *Service) {
	g.GET("/topru/config", s.configHandler)
	g.GET("/topru/summary", s.summaryHandler)
	g.GET("/topru/summaryplan", s.summaryPlanHandler)
	g.GET("/topru/user_stats", s.userStatsHandler)
}

func NewTopRuService(env string, o11yCli *o11y.Client) *Service {
	return &Service{Env: env, o11yCli: o11yCli}
}

type Service struct {
	Env     string
	o11yCli *o11y.Client
}

// @ID				getTopRUConfig
// @Summary		Get config of TopRU
// @Tags			topru
// @Description	TopRU is always enabled when using Delta Lake data source (O11Y diagnosisqueryswagger GET /topru/config).
// @Description	Clinic forwards to O11Y `GET /internal/diagnosis/api/v1/topru/config`. Cluster headers are required by Clinic to resolve the O11Y internal read endpoint; query string is passed through.
// @Description	Reference: https://github.com/tidbcloud/observability/blob/5c791d4717fe9fb90f26c0513444fb70a4e4957e/api/diagnosisqueryswagger/swagger.yaml
// @Produce		json
// @Param			X-Provider		header		string	true	"Cloud provider where RU data is stored (aws/gcp/azure); required by Clinic for cluster resolution"
// @Param			X-Region		header		string	true	"Cloud region; required by Clinic for cluster resolution"
// @Param			X-Org-Id		header		string	true	"Organization ID (x-org-id)"
// @Param			X-Project-Id	header		string	false	"Project ID (x-project-id; optional)"
// @Param			X-Cluster-Id	header		string	true	"Cluster ID (x-cluster-id)"
// @Param			X-Deploy-Type	header		string	true	"Deployment tier: dedicated/shared/nextgen/premium (and BYOC variants supported by Clinic)"
// @Success		200				{object}	topru.ConfigResponse
// @Router			/topru/config [get]
func (s *Service) configHandler(c *gin.Context) {
	var header ClusterHeader
	if err := c.BindHeader(&header); err != nil {
		log.Warn("failed to bind topru config header", zap.Error(err))
		_ = c.Error(apierr.ErrBadRequest.WrapWithNoMessage(err))
		return
	}
	if err := header.Validate(); err != nil {
		log.Warn("invalid topru config header", zap.Error(err))
		_ = c.Error(apierr.ErrBadRequest.WrapWithNoMessage(err))
		return
	}

	ctx, cancel := context.WithTimeout(c.Request.Context(), DefaultTimeout)
	defer cancel()

	err := utils.ProxyO11yByQueryCluster(ctx,
		c,
		s.o11yCli,
		header.DeployType,
		s.Env,
		*header.Provider,
		*header.Region,
		header.GetTenantID(),
		header.GetProjectID(),
		header.GetClusterID(),
		"/internal/diagnosis/api/v1/topru/config")
	if err != nil {
		log.Error("failed to proxy to o11y", zap.Error(err))
		_ = c.Error(apierr.ErrInternal.Wrap(err, "failed to proxy to o11y"))
		return
	}
}

// @ID				getTopRUSummary
// @Summary		Get TopRU summary (RU by SQL digest)
// @Tags			topru
// @Description	Aggregate ReadUnits (RU) grouped by sql_digest within the given time range. Plans are collapsed into one row per digest. Use /topru/summaryplan?digest=... for plan breakdown. Proxies to O11Y `GET /internal/diagnosis/api/v1/topru/summary`.
// @Description	Reference: https://github.com/tidbcloud/observability/blob/5c791d4717fe9fb90f26c0513444fb70a4e4957e/api/diagnosisqueryswagger/swagger.yaml
// @Produce		json
// @Param			X-Provider		header		string	true	"Cloud provider where RU data is stored (aws/gcp/azure)"
// @Param			X-Region		header		string	true	"Cloud region where RU data is stored"
// @Param			X-Org-Id		header		string	true	"Organization ID (x-org-id)"
// @Param			X-Project-Id	header		string	false	"Project ID (x-project-id; optional)"
// @Param			X-Cluster-Id	header		string	true	"Cluster ID (x-cluster-id)"
// @Param			X-Deploy-Type	header		string	true	"Deployment tier: dedicated/shared/nextgen/premium"
// @Param			start			query		string	true	"Start time in Unix seconds (inclusive)"
// @Param			end				query		string	true	"End time in Unix seconds (inclusive)"
// @Param			top				query		string	true	"Top N SQL digests to return"
// @Param			order_by		query		string	false	"Metric used to rank results: ru | exec_count | duration (default: ru)"
// @Param			user			query		string	false	"Omit to not filter by user. Pass empty (user=) for empty/NULL user only; pass a name to filter by that user"
// @Param			data_source		query		string	false	"Data source identifier (expected: deltalake)"
// @Param			step			query		int		false	"Bucket size in seconds. Default: 60"
// @Success		200				{object}	topru.SummaryEnvelope
// @Router			/topru/summary [get]
func (s *Service) summaryHandler(c *gin.Context) {
	var header ClusterHeader
	if err := c.BindHeader(&header); err != nil {
		log.Warn("failed to bind topru summary header", zap.Error(err))
		_ = c.Error(apierr.ErrBadRequest.WrapWithNoMessage(err))
		return
	}
	if err := header.Validate(); err != nil {
		log.Warn("invalid topru summary header", zap.Error(err))
		_ = c.Error(apierr.ErrBadRequest.WrapWithNoMessage(err))
		return
	}

	ctx, cancel := context.WithTimeout(c.Request.Context(), DefaultTimeout)
	defer cancel()

	err := utils.ProxyO11yByQueryCluster(ctx,
		c,
		s.o11yCli,
		header.DeployType,
		s.Env,
		*header.Provider,
		*header.Region,
		header.GetTenantID(),
		header.GetProjectID(),
		header.GetClusterID(),
		"/internal/diagnosis/api/v1/topru/summary")
	if err != nil {
		log.Error("failed to proxy to o11y", zap.Error(err))
		_ = c.Error(apierr.ErrInternal.Wrap(err, "failed to proxy to o11y"))
		return
	}
}

// @ID				getTopRUSummaryPlan
// @Summary		Get TopRU summary by plan (RU by SQL/plan digest)
// @Tags			topru
// @Description	Aggregate ReadUnits (RU) grouped by sql_digest and plan_digest. Optionally filter to one sql_digest via digest=. Proxies to O11Y `GET /internal/diagnosis/api/v1/topru/summaryplan`.
// @Description	Reference: https://github.com/tidbcloud/observability/blob/5c791d4717fe9fb90f26c0513444fb70a4e4957e/api/diagnosisqueryswagger/swagger.yaml
// @Produce		json
// @Param			X-Provider		header		string	true	"Cloud provider where RU data is stored (aws/gcp/azure)"
// @Param			X-Region		header		string	true	"Cloud region where RU data is stored"
// @Param			X-Org-Id		header		string	true	"Organization ID (x-org-id)"
// @Param			X-Project-Id	header		string	false	"Project ID (x-project-id; optional)"
// @Param			X-Cluster-Id	header		string	true	"Cluster ID (x-cluster-id)"
// @Param			X-Deploy-Type	header		string	true	"Deployment tier: dedicated/shared/nextgen/premium"
// @Param			start			query		string	true	"Start time in Unix seconds (inclusive)"
// @Param			end				query		string	true	"End time in Unix seconds (inclusive)"
// @Param			top				query		string	true	"Top N SQL/plan groups to return"
// @Param			digest			query		string	false	"Filter to a single sql_digest (drill-down from /topru/summary)"
// @Param			order_by		query		string	false	"Metric used to rank results: ru | exec_count | duration (default: ru)"
// @Param			user			query		string	false	"Omit to not filter by user. Pass empty (user=) for empty/NULL user only; pass a name to filter by that user"
// @Param			data_source		query		string	false	"Data source identifier (expected: deltalake)"
// @Param			step			query		int		false	"Bucket size in seconds. Default: 60"
// @Success		200				{object}	topru.SummaryPlanEnvelope
// @Router			/topru/summaryplan [get]
func (s *Service) summaryPlanHandler(c *gin.Context) {
	var header ClusterHeader
	if err := c.BindHeader(&header); err != nil {
		log.Warn("failed to bind topru summaryplan header", zap.Error(err))
		_ = c.Error(apierr.ErrBadRequest.WrapWithNoMessage(err))
		return
	}
	if err := header.Validate(); err != nil {
		log.Warn("invalid topru summaryplan header", zap.Error(err))
		_ = c.Error(apierr.ErrBadRequest.WrapWithNoMessage(err))
		return
	}

	ctx, cancel := context.WithTimeout(c.Request.Context(), DefaultTimeout)
	defer cancel()

	err := utils.ProxyO11yByQueryCluster(ctx,
		c,
		s.o11yCli,
		header.DeployType,
		s.Env,
		*header.Provider,
		*header.Region,
		header.GetTenantID(),
		header.GetProjectID(),
		header.GetClusterID(),
		"/internal/diagnosis/api/v1/topru/summaryplan")
	if err != nil {
		log.Error("failed to proxy to o11y", zap.Error(err))
		_ = c.Error(apierr.ErrInternal.Wrap(err, "failed to proxy to o11y"))
		return
	}
}

// @ID				getTopRUUserStats
// @Summary		Get TopRU user statistics (RU per user)
// @Tags			topru
// @Description	Aggregate RU and execution stats grouped by user over the given time range. Proxies to O11Y `GET /internal/diagnosis/api/v1/topru/user_stats`.
// @Description	Reference: https://github.com/tidbcloud/observability/blob/5c791d4717fe9fb90f26c0513444fb70a4e4957e/api/diagnosisqueryswagger/swagger.yaml
// @Produce		json
// @Param			X-Provider		header		string	true	"Cloud provider where RU data is stored (aws/gcp/azure)"
// @Param			X-Region		header		string	true	"Cloud region where RU data is stored"
// @Param			X-Org-Id		header		string	true	"Organization ID (x-org-id)"
// @Param			X-Project-Id	header		string	false	"Project ID (x-project-id; optional)"
// @Param			X-Cluster-Id	header		string	true	"Cluster ID (x-cluster-id)"
// @Param			X-Deploy-Type	header		string	true	"Deployment tier"
// @Param			start			query		string	true	"Start time in Unix seconds (inclusive)"
// @Param			end				query		string	true	"End time in Unix seconds (inclusive)"
// @Param			top				query		string	false	"Top N users to return (default: 20)"
// @Success		200				{object}	topru.UserStatsEnvelope
// @Router			/topru/user_stats [get]
func (s *Service) userStatsHandler(c *gin.Context) {
	var header ClusterHeader
	if err := c.BindHeader(&header); err != nil {
		log.Warn("failed to bind topru user_stats header", zap.Error(err))
		_ = c.Error(apierr.ErrBadRequest.WrapWithNoMessage(err))
		return
	}
	if err := header.Validate(); err != nil {
		log.Warn("invalid topru user_stats header", zap.Error(err))
		_ = c.Error(apierr.ErrBadRequest.WrapWithNoMessage(err))
		return
	}

	ctx, cancel := context.WithTimeout(c.Request.Context(), DefaultTimeout)
	defer cancel()

	err := utils.ProxyO11yByQueryCluster(ctx,
		c,
		s.o11yCli,
		header.DeployType,
		s.Env,
		*header.Provider,
		*header.Region,
		header.GetTenantID(),
		header.GetProjectID(),
		header.GetClusterID(),
		"/internal/diagnosis/api/v1/topru/user_stats")
	if err != nil {
		log.Error("failed to proxy to o11y", zap.Error(err))
		_ = c.Error(apierr.ErrInternal.Wrap(err, "failed to proxy to o11y"))
		return
	}
}
