package statements

import (
	"context"

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

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

const (
	statementV2DataSource = "v2"

	statementsDatasourceO11yPath  = "/internal/diagnosis/api/v1/statements/datasource"
	statementsFieldValuesO11yPath = "/internal/diagnosis/api/v1/statements/field_values"
	statementsListO11yPath        = "/internal/diagnosis/api/v1/statements/list"
	statementsPlansO11yPath       = "/internal/diagnosis/api/v1/statements/plans"
	statementsPlanDetailO11yPath  = "/internal/diagnosis/api/v1/statements/plan/detail"
	statementsSummaryO11yPath     = "/internal/diagnosis/api/v1/statements/summary"
	statementsTopStatsO11yPath    = "/internal/diagnosis/api/v1/statements/topstats"
)

// StatementsDatasourceResponse is returned by the O11Y datasource probe.
type StatementsDatasourceResponse struct {
	V2Available bool `json:"v2_available"`
}

func isStatementV2DataSource(dataSource string) bool {
	return dataSource == statementV2DataSource
}

func (s *Service) proxyStatementV2Handler(c *gin.Context, upstreamPath string) {
	header, ok := bindAndValidateStatementHeader(c)
	if !ok {
		return
	}
	s.proxyStatementToO11y(c, header, upstreamPath)
}

func bindAndValidateStatementHeader(c *gin.Context) (ClusterHeader, bool) {
	var header ClusterHeader
	if err := c.BindHeader(&header); err != nil {
		log.Warn(c.Request.Context(), "failed to bind statements proxy header", zap.Error(err))
		_ = c.Error(apierr.ErrBadRequest.WrapWithNoMessage(err))
		return ClusterHeader{}, false
	}
	if err := header.Validate(); err != nil {
		log.Warn(c.Request.Context(), "invalid statements proxy header", zap.Error(err))
		_ = c.Error(apierr.ErrBadRequest.WrapWithNoMessage(err))
		return ClusterHeader{}, false
	}
	return header, true
}

func (s *Service) proxyStatementToO11y(c *gin.Context, header ClusterHeader, upstreamPath string) {
	s.proxyStatementToO11yWithModifier(c, header, upstreamPath, nil)
}

func (s *Service) proxyStatementToO11yWithModifier(
	c *gin.Context,
	header ClusterHeader,
	upstreamPath string,
	modifier utils.ResponseModifier,
) {
	ctx, cancel := context.WithTimeout(c.Request.Context(), DefaultTimeout)
	defer cancel()

	err := utils.ProxyO11yByQueryClusterWithModifier(
		ctx,
		c,
		s.o11yCli,
		*header.DeployType,
		s.Env,
		*header.Provider,
		*header.Region,
		header.GetTenantID(),
		header.GetProjectID(),
		header.GetClusterID(),
		upstreamPath,
		modifier,
	)
	if err != nil {
		log.Error(ctx, "failed to proxy statements request to o11y", zap.String("upstream_path", upstreamPath), zap.Error(err))
		_ = c.Error(apierr.ErrInternal.Wrap(err, "failed to proxy to o11y"))
	}
}

// @ID				getStatementsDatasource
// @Summary		Probe the Statements V2 datasource
// @Tags			statements
// @Description	Proxies to O11Y GET /internal/diagnosis/api/v1/statements/datasource. Use before querying with data_source=v2.
// @Produce		json
// @Param			X-Provider		header		string	true	"Cloud provider"
// @Param			X-Region		header		string	true	"Cloud region"
// @Param			X-Org-Id		header		string	true	"Organization ID"
// @Param			X-Project-Id	header		string	false	"Project ID"
// @Param			X-Cluster-Id	header		string	true	"Cluster ID"
// @Param			X-Deploy-Type	header		string	true	"Deployment tier"
// @Success		200				{object}	StatementsDatasourceResponse
// @Router			/statements/datasource [get]
func (s *Service) DatasourceHandler(c *gin.Context) {
	s.proxyStatementV2Handler(c, statementsDatasourceO11yPath)
}

// @ID				getStatementFieldValues
// @Summary		Get distinct Statement field values
// @Tags			statements
// @Description	Transparently proxies the complete query string to O11Y GET /internal/diagnosis/api/v1/statements/field_values.
// @Produce		json
// @Param			X-Provider		header	string		true	"Cloud provider"
// @Param			X-Region		header	string		true	"Cloud region"
// @Param			X-Org-Id		header	string		true	"Organization ID"
// @Param			X-Project-Id	header	string		false	"Project ID"
// @Param			X-Cluster-Id	header	string		true	"Cluster ID"
// @Param			X-Deploy-Type	header	string		true	"Deployment tier"
// @Param			begin_time		query	int			true	"Start time in Unix seconds"
// @Param			end_time		query	int			true	"End time in Unix seconds"
// @Param			field			query	string		true	"Field name"
// @Param			limit			query	int			false	"Maximum number of distinct values"
// @Param			schemas			query	[]string	false	"Schema filters"			collectionFormat(multi)
// @Param			stmt_types		query	[]string	false	"Statement type filters"	collectionFormat(multi)
// @Param			data_source		query	string		false	"Datasource selector (v2)"
// @Success		200				{array}	string
// @Router			/statements/field_values [get]
func (s *Service) FieldValuesHandler(c *gin.Context) {
	s.proxyStatementV2Handler(c, statementsFieldValuesO11yPath)
}

// @ID				getStatementsSummary
// @Summary		Get Statement summary statistics
// @Tags			statements
// @Description	Transparently proxies the complete query string to O11Y GET /internal/diagnosis/api/v1/statements/summary.
// @Produce		json
// @Param			X-Provider		header		string		true	"Cloud provider"
// @Param			X-Region		header		string		true	"Cloud region"
// @Param			X-Org-Id		header		string		true	"Organization ID"
// @Param			X-Project-Id	header		string		false	"Project ID"
// @Param			X-Cluster-Id	header		string		true	"Cluster ID"
// @Param			X-Deploy-Type	header		string		true	"Deployment tier"
// @Param			begin_time		query		int			true	"Start time in Unix seconds"
// @Param			end_time		query		int			true	"End time in Unix seconds"
// @Param			schemas			query		[]string	false	"Schema filters"			collectionFormat(multi)
// @Param			stmt_types		query		[]string	false	"Statement type filters"	collectionFormat(multi)
// @Param			top				query		int			false	"Top N results"
// @Param			order_by		query		string		false	"Sort metric"
// @Param			dimension		query		string		false	"Grouping dimension"
// @Param			step			query		int			false	"Time bucket size in seconds"
// @Param			data_source		query		string		false	"Datasource selector (v2)"
// @Success		200				{object}	map[string]interface{}
// @Router			/statements/summary [get]
func (s *Service) SummaryHandler(c *gin.Context) {
	s.proxyStatementV2Handler(c, statementsSummaryO11yPath)
}

// @ID				getStatementsTopStats
// @Summary		Get Statement top statistics
// @Tags			statements
// @Description	Transparently proxies the complete query string to O11Y GET /internal/diagnosis/api/v1/statements/topstats.
// @Produce		json
// @Param			X-Provider		header		string	true	"Cloud provider"
// @Param			X-Region		header		string	true	"Cloud region"
// @Param			X-Org-Id		header		string	true	"Organization ID"
// @Param			X-Project-Id	header		string	false	"Project ID"
// @Param			X-Cluster-Id	header		string	true	"Cluster ID"
// @Param			X-Deploy-Type	header		string	true	"Deployment tier"
// @Param			begin_time		query		int		true	"Start time in Unix seconds"
// @Param			end_time		query		int		true	"End time in Unix seconds"
// @Param			top				query		int		false	"Top N results"
// @Param			dimension		query		string	false	"Grouping dimension"
// @Param			order_by		query		string	false	"Sort metric"
// @Param			data_source		query		string	false	"Datasource selector (v2)"
// @Success		200				{object}	map[string]interface{}
// @Router			/statements/topstats [get]
func (s *Service) TopStatsHandler(c *gin.Context) {
	s.proxyStatementV2Handler(c, statementsTopStatsO11yPath)
}
