package server

import (
	"context"
	"net/url"
	"path"
	"strings"

	"github.com/Masterminds/semver"
	"github.com/samber/lo"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/log"

	"github.com/tidbcloud/cluster-controller/server/internal/util"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
)

const (
	// ResponseKeyStatusCode is the key of the HTTP status code in the response header of gRPC.
	// if we return an error in the gRPC service, the HTTP response body will be something like
	// `{"code":2, "message":"...", "details":[]}`.
	// so we choose to return a nil error and set the HTTP status code in the response header of gRPC.
	ResponseKeyStatusCode = "x-http-code"

	// default timeout for http request
	httpTimeout = 5
)

var auditSidecarDynamicLogPathVersionConstraint, _ = semver.NewConstraint(">= v1.0.0")

func GetClusterCR(ctx context.Context, reader util.ClusterReader, cli client.Client, clusterId string) (*cloudv1.Cluster, error) {
	cluster, err := reader.Get(ctx, cli, clusterId)
	if err != nil {
		tilog.Error(ctx, "Failed to get cluster", zap.Error(err), zap.String("cluster", clusterId))
		if apierrors.IsNotFound(err) {
			tilog.Info(ctx, "Cluster not found", zap.String("cluster", clusterId))
			return nil, status.Errorf(codes.NotFound, "Cluster %s not found", clusterId)
		}
		tilog.Error(ctx, "Failed to get cluster", zap.Error(err))
		return nil, status.Errorf(codes.Internal, "Failed to get cluster %s: %s", clusterId, err)
	}
	return cluster, nil
}

// allocateDBAuditLogBucketURLInSharedBucket allocates a prefix in the using shared bucket.
func allocateDBAuditLogBucketURLInSharedBucket(cluster *cloudv1.Cluster) string {
	u := lo.Must1(url.Parse(cluster.Spec.SharedBucket.BucketURL))
	p := path.Join(u.Path, consts.SharedBucketPrefixDBAuditLog)
	if !strings.HasSuffix(p, "/") {
		p += "/"
	}
	u.Path = p
	return u.String()
}

func CheckIfAuditSidecarSupportDynamicLogPath(ctx context.Context, v string) bool {
	return CheckIfVersionSupported(ctx, auditSidecarDynamicLogPathVersionConstraint, v)
}

func CheckIfVersionSupported(ctx context.Context, constraint *semver.Constraints, v string) bool {
	logger := log.FromContext(ctx)

	version, err := semver.NewVersion(v)
	if err != nil {
		logger.Error(err, "failed to parse version for constraint", "constraint", constraint, "version", v)
		return false
	}

	return constraint.Check(version)
}
