package tidbtools

import (
	"errors"
	"fmt"
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/devops/api"
	tidbapi "github.com/tidbcloud/devops/api/tidbtools"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
)

// cluster_type allowlist. Values match ccproto.ClusterType string names;
// typed constants prevent silent drift between routing and the api layer.
const (
	dedicatedType  tidbapi.ClusterType = tidbapi.ClusterTypeDedicatedTiDB
	sharedPoolType tidbapi.ClusterType = tidbapi.ClusterTypeSharedPool
	logicalType    tidbapi.ClusterType = tidbapi.ClusterTypeLogicalCluster
)

// isPremium reports whether the given cluster_type is one of the
// Premium/Next-Gen flavours used by the Tools page.
func isPremium(clusterType tidbapi.ClusterType) bool {
	return clusterType == sharedPoolType || clusterType == logicalType
}

// supportsBackupRestore reports whether BR records are available for the
// cluster. Shared pools are infrastructure and do not own backup records; the
// NextGen APIs in this controller are currently supported only by Premium and
// Essential v2 logical clusters.
func supportsBackupRestore(clusterType tidbapi.ClusterType, servicePlan string) bool {
	return clusterType == dedicatedType ||
		(clusterType == logicalType && (servicePlan == "Premium" || servicePlan == "Essential_V2"))
}

// Paging defaults. `maxPageSize` caps what the FE can request.
const (
	defaultPage     = int32(1)
	defaultPageSize = int32(20)
	maxPageSize     = int32(200)
	// The Premium console API caps ListTidbBackups at 100. Its offset is
	// relative to the requested page size, so forwarding a larger value would
	// skip records between pages.
	maxPremiumBackupPageSize = int32(100)
)

// parseClusterID extracts and validates :cluster_id from the route.
func parseClusterID(c *gin.Context) (uint64, error) {
	s := c.Param("cluster_id")
	if s == "" {
		return 0, errors.New("cluster_id is required")
	}
	id, err := strconv.ParseUint(s, 10, 64)
	if err != nil {
		return 0, fmt.Errorf("invalid cluster_id: %w", err)
	}
	return id, nil
}

// parsePaging validates `page_size` against `maxPageSize` (returns an error
// when page_size exceeds maxPageSize) and fills defaults.
// It writes INVALID_ARGUMENT directly on the context when the query
// string is malformed; the caller should bail out on the returned
// error without writing another response.
func parsePaging(c *gin.Context) (int32, int32, error) {
	page := defaultPage
	pageSize := defaultPageSize
	if v := c.Query("page"); v != "" {
		n, err := strconv.ParseInt(v, 10, 32)
		if err != nil || n < 1 {
			return 0, 0, fmt.Errorf("invalid page: %q", v)
		}
		page = int32(n)
	}
	if v := c.Query("page_size"); v != "" {
		n, err := strconv.ParseInt(v, 10, 32)
		if err != nil || n < 1 {
			return 0, 0, fmt.Errorf("invalid page_size: %q", v)
		}
		if n > int64(maxPageSize) {
			return 0, 0, fmt.Errorf("page_size %d exceeds max %d", n, maxPageSize)
		}
		pageSize = int32(n)
	}
	return page, pageSize, nil
}

func validatePremiumBackupPageSize(pageSize int32) error {
	if pageSize > maxPremiumBackupPageSize {
		return fmt.Errorf("page_size must not exceed %d for Premium backups", maxPremiumBackupPageSize)
	}
	return nil
}

// notImplemented is a helper for routes whose upstream RPC is not yet
// available (see design doc §11). Returns HTTP 501 to let the FE show
// a friendly "coming soon" state during rollout.
func (c *TidbToolsController) notImplemented(ctx *gin.Context, feature string) {
	ctrl.FailWithCode(ctx, fmt.Errorf("%s: upstream RPC not yet available", feature), api.ErrCodeNotImplemented)
}

// requireClusterScope is the prelude for endpoints that only need
// :cluster_id and a permission check (e.g. ListChangefeeds).
// On error it writes the response and returns ok=false.
func (c *TidbToolsController) requireClusterScope(ctx *gin.Context) (uint64, bool) {
	clusterID, err := parseClusterID(ctx)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeInvalidArgument)
		return 0, false
	}
	if err := ctrl.CheckClusterScope(ctx, c.coreSvcCli, clusterID); err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodePermissionDenied)
		return 0, false
	}
	return clusterID, true
}

// requireClusterAccess is the standard handler prelude for endpoints
// that dispatch on cluster type. It parses :cluster_id, enforces caller
// scope and resolves cluster info via resolveClusterInfo. On any failure
// it writes the appropriate error response and returns ok=false; the
// caller must return immediately without writing again.
func (c *TidbToolsController) requireClusterAccess(ctx *gin.Context) (uint64, clusterInfo, bool) {
	clusterID, ok := c.requireClusterScope(ctx)
	if !ok {
		return 0, clusterInfo{}, false
	}
	ci, err := c.resolveClusterInfo(ctx.Request.Context(), clusterID)
	if err != nil {
		ctrl.Fail(ctx, err)
		return 0, clusterInfo{}, false
	}
	return clusterID, ci, true
}
