package sqls

import (
	"net/http"

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

// StatementsFieldValuesQuery binds GET /statements/field_values query parameters.
type StatementsFieldValuesQuery struct {
	BeginTime   *int     `form:"begin_time"`
	EndTime     *int     `form:"end_time"`
	Field       string   `form:"field"`
	Limit       int      `form:"limit"`
	Schemas     []string `form:"schemas"`
	StmtTypes   []string `form:"stmt_types"`
	Text        string   `form:"text"`
	SampleUser  string   `form:"sample_user"`
	SampleUsers []string `form:"sample_users"`
	Tables      []string `form:"tables"`
	MatchField  string   `form:"match_field"`
	MatchValues []string `form:"match_values"`
	Filters     string   `form:"filters"`
	DataSource  string   `form:"dataSource"`
	DataSource2 string   `form:"data_source"`
}

func bindStatementsValues(c *gin.Context, values *[]string) {
	*values = mergeQueryStringSlices(*values, queryMultiValue(c, "values", "values[]"))
}

func bindStatementsFieldFilters(c *gin.Context, schemas, stmtTypes, matchValues *[]string) {
	bindSchemasAndStmtTypes(c, schemas, stmtTypes)
	bindStatementsMatchValues(c, matchValues)
}

func bindStatementsMatchValues(c *gin.Context, matchValues *[]string) {
	*matchValues = mergeQueryStringSlices(*matchValues, queryMultiValue(c, "match_values", "match_values[]"))
}

// @Summary		Distinct statement field values
// @Tags			statements
// @Description	Returns distinct values of a field within the time window (JSON string array). Use GET /statements/available_fields?usage=filter for allowed field names.
// @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 (inclusive)"
// @Param			end_time		query		int			true	"End time in Unix seconds (inclusive)"
// @Param			field			query		string		true	"Field name; see available_fields?usage=filter"
// @Param			limit			query		int			false	"Max distinct values (default 500, max 2000)"
// @Param			schemas			query		[]string	false	"Filter by schemas"
// @Param			stmt_types		query		[]string	false	"Filter by stmt_types"
// @Param			text			query		string		false	"Substring filter on SQL text or digest"
// @Param			sample_user		query		string		false	"Filter rows by user; use sample_users for multi-select"
// @Param			sample_users	query		[]string	false	"Filter rows by any of the users (supports sample_users[] / users[])"
// @Param			tables			query		[]string	false	"Filter rows by table_names (supports tables[] / table_names[])"
// @Param			match_field		query		string		false	"Narrow rows by another field before distinct"
// @Param			match_values	query		[]string	false	"Values for match_field"
// @Param			filters			query		string		false	"Additional field filters as JSON array"
// @Success		200	{array}		string
// @Router			/statements/field_values [get]
func (s *Service) StatementsFieldValuesHandler(c *gin.Context) {
	var header ClusterHeader
	if err := c.BindHeader(&header); err != nil {
		log.Warn("failed to bind statements field values header", zap.Error(err))
		c.JSON(http.StatusBadRequest, errorResponse{Error: true, Message: "Bad Request", FullText: err.Error()})
		return
	}
	if err := header.Validate(); err != nil {
		log.Warn("invalid statements field values header", zap.Error(err))
		c.JSON(http.StatusBadRequest, errorResponse{Error: true, Message: "Bad Request", FullText: err.Error()})
		return
	}

	var q StatementsFieldValuesQuery
	if err := c.BindQuery(&q); err != nil {
		log.Warn("failed to bind statements field values query", zap.Error(err))
		c.JSON(http.StatusBadRequest, errorResponse{Error: true, Message: "Bad Request", FullText: err.Error()})
		return
	}
	bindStatementsFieldFilters(c, &q.Schemas, &q.StmtTypes, &q.MatchValues)
	bindStatementsUsers(c, &q.SampleUsers)
	bindStatementsTables(c, &q.Tables)
	if q.BeginTime == nil || q.EndTime == nil {
		c.JSON(http.StatusBadRequest, errorResponse{Error: true, Message: "Bad Request", FullText: "begin_time and end_time are required"})
		return
	}
	if err := validateStatementsDistinctField(q.Field); err != nil {
		c.JSON(http.StatusBadRequest, errorResponse{Error: true, Message: "Bad Request", FullText: err.Error()})
		return
	}
	extraFilters, err := parseStatementsFiltersJSON(q.Filters)
	if err != nil {
		c.JSON(http.StatusBadRequest, errorResponse{Error: true, Message: "Bad Request", FullText: err.Error()})
		return
	}
	filterOpts := statementsFilterOptions{
		beginTime:    *q.BeginTime,
		endTime:      *q.EndTime,
		schemas:      q.Schemas,
		stmtTypes:    q.StmtTypes,
		text:         q.Text,
		sampleUser:   q.SampleUser,
		sampleUsers:  q.SampleUsers,
		tables:       q.Tables,
		field:        q.MatchField,
		values:       q.MatchValues,
		extraFilters: extraFilters,
	}
	if err := validateStatementsQueryFilters(filterOpts); err != nil {
		c.JSON(http.StatusBadRequest, errorResponse{Error: true, Message: "Bad Request", FullText: err.Error()})
		return
	}

	if *header.DeployType == "byoc" {
		*header.BizType = "byoc"
		*header.DeployType = "nextgen"
	}

	dataSource := normalizeDataSource(q.DataSource, q.DataSource2)
	filterOpts.dataSource = dataSource
	filter := buildStatementsFilter(filterOpts)

	params := &StatementsFieldValuesParams{
		Env:        *header.Env,
		Provider:   *header.Provider,
		Region:     *header.Region,
		TenantID:   *header.TenantID,
		ProjectID:  *header.ProjectID,
		ClusterID:  *header.ClusterID,
		SharedTier: *header.DeployType == TierShared,
		NextGen:    *header.DeployType == TierNextGen || *header.DeployType == TierPremium,
		Byoc:       *header.BizType == "byoc",
		Filter:     filter,
		Field:      normalizeStatementsFilterField(q.Field),
		Limit:      parseStatementsFieldValuesLimit(q.Limit),
		DataSource: dataSource,
	}

	values, err := s.SQLsDuckDBAdapter.QueryStatementsFieldValues(c.Request.Context(), params)
	if err != nil {
		log.Warn("failed to query statements field values", zap.Error(err))
		c.JSON(http.StatusInternalServerError, errorResponse{Error: true, Message: "Internal Server Error", FullText: err.Error()})
		return
	}

	if values == nil {
		values = []string{}
	}
	c.JSON(http.StatusOK, values)
}
