package topsql

import (
	"context"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/log"
	"go.uber.org/zap"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/observability/o11ydiagnosis/pkg/topsql/api"
	"github.com/tidbcloud/observability/o11ydiagnosis/pkg/topsql/errors"
	"github.com/tidbcloud/observability/o11ydiagnosis/pkg/topsql/metacache"
	"github.com/tidbcloud/observability/o11ydiagnosis/pkg/topsql/vm"
)

const (
	DefaultTimeout = time.Second * 180
)

func InitTopSQLApi(g *gin.RouterGroup, s *Service) {
	g.GET("/topsql/config", s.configHandler)
	g.GET("/topsql/instances", s.instancesHandler)
	g.GET("/topsql/summary", s.summaryHandler)
}

func NewTopSQLService(httpc *http.Client, kubec client.Client, oldClusters map[string]struct{}) *Service {
	return &Service{Query: vm.NewVmQuery(httpc, metacache.NewMetaCache(kubec), oldClusters)}
}

type Service struct {
	Query api.Query
}

type configResponse struct {
	Enable bool `json:"enable"`
}

type summaryEnvelope struct {
	Status string            `json:"status"`
	Data   []api.SummaryItem `json:"data"`
}

type instancesEnvelope struct {
	Status string             `json:"status"`
	Data   []api.InstanceItem `json:"data"`
}

// @Summary		Get TopSQL configuration
// @Tags			topsql
// @Description	Return the TopSQL feature configuration for diagnosis queries.
// @Produce		json
// @Success		200	{object}	configResponse
// @Router			/topsql/config [get]
func (s *Service) configHandler(c *gin.Context) {
	c.JSON(http.StatusOK, configResponse{true})
}

// ConfigHandler is a public wrapper for configHandler
func (s *Service) ConfigHandler(c *gin.Context) {
	s.configHandler(c)
}

// @Summary		Get TopSQL summary
// @Tags			topsql
// @Description	Return aggregated TopSQL summary for a given time window and instance filter.
// @Produce		json
// @Param			X-Provider		header		string	false	"Cloud provider where TopSQL data is stored (aws or gcp)"
// @Param			X-Region		header		string	false	"Cloud region where TopSQL data is stored"
// @Param			X-Org-Id		header		string	true	"Organization ID (x-org-id) used to locate TopSQL data"
// @Param			X-Project-Id	header		string	false	"Project ID in TiDB Cloud (optional)"
// @Param			X-Cluster-Id	header		string	true	"Cluster ID (x-cluster-id) of the target TiDB deployment"
// @Param			start			query		string	true	"Start time in Unix seconds (inclusive)"
// @Param			end				query		string	true	"End time in Unix seconds (inclusive)"
// @Param			window			query		string	true	"Aggregation window duration (e.g. 5m, 1h)"
// @Param			top				query		int		true	"Top N results to return"
// @Param			instance		query		string	true	"Instance name filter (e.g. tidb_instance_1)"
// @Param			instance_type	query		string	true	"Instance type filter (e.g. tidb, tikv, pd)"
// @Success		200				{object}	summaryEnvelope
// @Router			/topsql/summary [get]
func (s *Service) summaryHandler(c *gin.Context) {
	var header ClusterHeader
	if err := c.BindHeader(&header); err != nil {
		log.Warn("failed to bind topsql summary header", zap.Error(err))
		_ = c.Error(errors.ErrBadRequest.Wrap(err, "failed to bind topsql summary header"))
		return
	}
	if err := header.Validate(); err != nil {
		log.Warn("invalid topsql summary header", zap.Error(err))
		_ = c.Error(errors.ErrBadRequest.Wrap(err, "invalid topsql summary header"))
		return
	}

	var query SummaryQuery
	if err := c.BindQuery(&query); err != nil {
		log.Warn("failed to bind topsql summary query", zap.Error(err))
		_ = c.Error(errors.ErrBadRequest.Wrap(err, "failed to bind topsql summary query"))
		return
	}
	if err := query.Validate(); err != nil {
		log.Warn("invalid topsql summary query", zap.Error(err))
		_ = c.Error(errors.ErrBadRequest.Wrap(err, "invalid topsql summary query"))
		return
	}

	env := "prod"
	if header.Env != nil && *header.Env != "" {
		env = *header.Env
	}
	if header.DeployType == "byoc" {
		header.BizType = "byoc"
		header.DeployType = "nextgen"
	}
	nextGen := (strings.ToLower(header.DeployType) == "nextgen") || (strings.ToLower(header.DeployType) == "premium")
	nextGenHost := strings.EqualFold(header.DeployType, "nextgen-host")

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

	params := &api.SummaryParams{
		Env:         env,
		TenantID:    *header.TenantID,
		ClusterID:   *header.ClusterID,
		NextGen:     nextGen,
		NextGenHost: nextGenHost,
		Byoc:        header.BizType == "byoc",

		StartSecs:    int(*query.StartSecs),
		EndSecs:      int(*query.EndSecs),
		WindowSecs:   int(*query.Window / time.Second),
		Top:          *query.Top,
		Instance:     *query.Instance,
		InstanceType: *query.InstanceType,
	}
	var items []api.SummaryItem
	var err error
	if items, err = s.Query.Summary(ctx, params); err != nil {
		log.Warn("failed to call topsql summary", zap.Error(err))
		_ = c.Error(err)
		return
	}

	c.JSON(http.StatusOK, summaryEnvelope{
		Status: "ok",
		Data:   items,
	})
}

// SummaryHandler is a public wrapper for summaryHandler
func (s *Service) SummaryHandler(c *gin.Context) {
	s.summaryHandler(c)
}

// @Summary		Get available TopSQL instances
// @Tags			topsql
// @Description	List TopSQL instances that have data within the given time window.
// @Produce		json
// @Param			X-Provider		header		string	false	"Cloud provider where TopSQL data is stored (aws or gcp)"
// @Param			X-Region		header		string	false	"Cloud region where TopSQL data is stored"
// @Param			X-Org-Id		header		string	true	"Organization ID (x-org-id) used to locate TopSQL data"
// @Param			X-Project-Id	header		string	false	"Project ID in TiDB Cloud (optional)"
// @Param			X-Cluster-Id	header		string	true	"Cluster ID (x-cluster-id) of the target TiDB deployment"
// @Param			start			query		string	true	"Start time in Unix seconds (inclusive)"
// @Param			end				query		string	true	"End time in Unix seconds (inclusive)"
// @Success		200				{object}	instancesEnvelope
// @Router			/topsql/instances [get]
func (s *Service) instancesHandler(c *gin.Context) {
	var header ClusterHeader
	if err := c.BindHeader(&header); err != nil {
		log.Warn("failed to bind topsql instances header", zap.Error(err))
		_ = c.Error(errors.ErrBadRequest.Wrap(err, "failed to bind topsql instances header"))
		return
	}
	if err := header.Validate(); err != nil {
		log.Warn("invalid topsql instances header", zap.Error(err))
		_ = c.Error(errors.ErrBadRequest.Wrap(err, "invalid topsql instances header"))
		return
	}

	var query InstanceQuery
	if err := c.BindQuery(&query); err != nil {
		log.Warn("failed to bind topsql instances query", zap.Error(err))
		_ = c.Error(errors.ErrBadRequest.Wrap(err, "failed to bind topsql instances query"))
		return
	}
	if err := query.Validate(); err != nil {
		log.Warn("invalid topsql instances query", zap.Error(err))
		_ = c.Error(errors.ErrBadRequest.Wrap(err, "invalid topsql instances query"))
		return
	}

	env := "prod"
	if header.Env != nil && *header.Env != "" {
		env = *header.Env
	}
	if header.DeployType == "byoc" {
		header.BizType = "byoc"
		header.DeployType = "nextgen"
	}
	nextGen := (strings.ToLower(header.DeployType) == "nextgen") || (strings.ToLower(header.DeployType) == "premium")
	nextGenHost := strings.EqualFold(header.DeployType, "nextgen-host")

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

	params := &api.InstancesParams{
		Env:         env,
		TenantID:    *header.TenantID,
		ClusterID:   *header.ClusterID,
		NextGen:     nextGen,
		NextGenHost: nextGenHost,
		Byoc:        header.BizType == "byoc",

		StartSecs: int(*query.StartSecs),
		EndSecs:   int(*query.EndSecs),
	}
	var instances []api.InstanceItem
	var err error
	if instances, err = s.Query.Instances(ctx, params); err != nil {
		log.Warn("failed to call topsql instance", zap.Error(err))
		_ = c.Error(err)
		return
	}

	c.JSON(http.StatusOK, instancesEnvelope{
		Status: "ok",
		Data:   instances,
	})
}

// InstancesHandler is a public wrapper for instancesHandler
func (s *Service) InstancesHandler(c *gin.Context) {
	s.instancesHandler(c)
}
