package cluster

import (
	"context"
	"fmt"
	"strings"

	"github.com/gogo/protobuf/types"
	"github.com/google/uuid"
	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"github.com/tidbcloud/metrics-server/central/config"
	"github.com/tidbcloud/metrics-server/central/db"
	"github.com/tidbcloud/metrics-server/central/db/dbutil"
	"github.com/tidbcloud/metrics-server/central/db/entity"
	"github.com/tidbcloud/metrics-server/central/model/cluster"
	"github.com/tidbcloud/metrics-server/central/service"
	"github.com/tidbcloud/metrics-server/idl/pbgen/proto/central"
	"github.com/tidbcloud/pkgv2/log"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.opentelemetry.io/otel"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type clusterService struct {
	central.UnimplementedClusterServiceServer
	service.OperationsService
	clustersModel cluster.ModelV2
}

var _ central.ClusterServiceServer = (*clusterService)(nil)
var Tracer = otel.Tracer("central/service/cluster")

// NewService constructs a ClusterService.
func NewService(s service.OperationsService, model cluster.ModelV2) central.ClusterServiceServer {
	return &clusterService{
		OperationsService: s,
		clustersModel:     model,
	}
}

func (s *clusterService) userCluster(ctx context.Context) cluster.UserContextV2 {
	user := s.CurrentUser(ctx)
	return s.clustersModel.User(ctx, user)
}

func (s *clusterService) Log(ctx context.Context) *zap.Logger {
	return s.userCluster(ctx).Log().With(log.FieldsFromContext(ctx)...)
}

func getKey(ctx context.Context) string {
	return strings.ReplaceAll(uuid.New().String(), "-", "")
}

func (s *clusterService) CreateMetricsKey(ctx context.Context, req *central.CreateMetricsKeyReq) (resp *central.CreateMetricsKeyResp, err error) {
	defer func() {
		details := auditlog.Details{
			auditlog.KeyError: err,
		}
		eventSubmitErr := auditlog.NewEvent(ctx, auditlog.CreatePrometheusIntegration).SetResult(err == nil).SetDetails(details.Build()).SetOrgID(req.OrgId).SetProjectID(req.ProjectId).Submit()
		if eventSubmitErr != nil {
			user := s.CurrentUser(ctx)
			log.Error(ctx, "CreateMetricsKey submit event fail",
				zap.Uint64("tenantID", user.TenantID.Uint64()),
				zap.Uint64("userID", user.ID.Uint64()),
				zap.Error(eventSubmitErr))
		}
	}()

	userContext := s.userCluster(ctx)
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	err = userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}

	user := s.CurrentUser(ctx)

	key := getKey(ctx)
	var metricsKey *entity.MetricsKey
	err = db.Transaction(func(tx *gorm.DB) error {
		allExistsMetricsKey, err := dbutil.GetAllMetricsKeyByProject(tx, projectID)
		if err != nil && err != gorm.ErrRecordNotFound {
			return err
		}

		if len(allExistsMetricsKey) >= config.MaxMetricsKeyCount {
			return errcode.NewInvalidInputErr(fmt.Errorf(
				"more than %d active scrape_config files is not allowed. You must first delete a file to continue",
				config.MaxMetricsKeyCount,
			))
		}

		metricsKey, err = dbutil.CreateMetricsKey(tx, projectID, user.ID, key)
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return nil, err
	}
	t, err := types.TimestampProto(metricsKey.CreatedAt)
	if err != nil {
		return nil, err
	}

	return &central.CreateMetricsKeyResp{Key: &central.MetricsKey{KeyId: metricsKey.ID.Uint64(), Key: key, CreatedAt: t}}, nil
}

func (s *clusterService) GetAllMetricsKey(ctx context.Context, req *central.GetAllMetricsKeyReq) (*central.GetAllMetricsKeyResp, error) {
	userContext := s.userCluster(ctx)
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}

	var metricsKeys []entity.MetricsKey

	if err = db.Transaction(func(tx *gorm.DB) error {
		metricsKeys, err = dbutil.GetAllMetricsKeyByProject(tx, projectID)
		return err
	}); err != nil {
		return nil, err
	}

	resp := central.GetAllMetricsKeyResp{}

	for _, metricsKey := range metricsKeys {
		t, err := types.TimestampProto(metricsKey.CreatedAt)
		if err != nil {
			return nil, err
		}

		resp.MetricsKeys = append(resp.MetricsKeys, &central.MetricsKey{KeyId: metricsKey.ID.Uint64(), Key: "", CreatedAt: t})
	}

	return &resp, nil
}

func (s *clusterService) DeleteMetricsKey(ctx context.Context, req *central.DeleteMetricsKeyReq) (resp *types.Empty, err error) {
	defer func() {
		details := auditlog.Details{
			auditlog.KeyError: err,
		}
		eventSubmitErr := auditlog.NewEvent(ctx, auditlog.DeletePrometheusIntegration).SetResult(err == nil).SetDetails(details.Build()).SetOrgID(req.OrgId).SetProjectID(req.ProjectId).Submit()
		if eventSubmitErr != nil {
			user := s.CurrentUser(ctx)
			log.Error(ctx, "DeleteMetricsKey submit event fail",
				zap.Uint64("tenantID", user.TenantID.Uint64()),
				zap.Uint64("userID", user.ID.Uint64()),
				zap.Error(eventSubmitErr))
		}
	}()

	logger := s.Log(ctx)
	userContext := s.userCluster(ctx)
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	err = userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}

	keyID := entity.ID(req.KeyId)

	var metricsKey *entity.MetricsKey
	err = db.Transaction(func(tx *gorm.DB) error {
		metricsKey, err = dbutil.GetMetricsKeyByID(tx, keyID, projectID)
		return err
	})
	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, errcode.NewInternalErr(errors.New("get key failed"))
	}

	if metricsKey == nil || metricsKey.ID == 0 {
		logger.Error("not found metrics key", zap.String("req", req.String()), zap.Uint64("projectID", projectID.Uint64()))
		return nil, errcode.NewForbiddenErr(errors.New("only owner can perform this operation"))
	}

	if err := db.Transaction(func(tx *gorm.DB) error {
		if err := dbutil.DeleteMetricsKey(tx, metricsKey.ID, projectID); err != nil {
			return err
		}

		return nil
	}); err != nil {
		return nil, err
	}

	return &types.Empty{}, nil
}
