package clusterencryptionbinding

import (
	"context"

	"github.com/tidbcloud/pkgv2/db"
	"go.opentelemetry.io/otel"
	"gorm.io/gorm/clause"

	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	bindingrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/clusterencryptionbinding"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

var tracer = otel.Tracer("cluster_encryption_binding_repo_impl")

type repoImpl struct {
	provider db.Provider
}

func NewClusterEncryptionBindingRepo(provider db.Provider) bindingrepo.Repo {
	return &repoImpl{provider: provider}
}

func (r *repoImpl) Upsert(ctx context.Context, binding *entity.ClusterEncryptionBinding) bizerr.BizError {
	_, span := tracer.Start(ctx, "Upsert")
	defer span.End()

	m := entityToModel(binding)
	result := r.provider.WriteDB(ctx).
		Clauses(clause.OnConflict{
			Columns: []clause.Column{{Name: "cluster_id"}},
			DoUpdates: clause.AssignmentColumns([]string{
				"encryption_key_id", "state", "message", "dag_instance_id", "updated_at",
			}),
		}).
		Create(m)
	if result.Error != nil {
		return bizerr.WrapGormError(result.Error, "failed to upsert cluster encryption binding")
	}
	return nil
}

func (r *repoImpl) LoadByClusterId(ctx context.Context, clusterId uint64) (*entity.ClusterEncryptionBinding, bizerr.BizError) {
	_, span := tracer.Start(ctx, "LoadByClusterId")
	defer span.End()

	var m schema.ClusterEncryptionBinding
	if err := r.provider.ReadDB(ctx).Where("cluster_id = ?", clusterId).First(&m).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to load cluster encryption binding for cluster %d", clusterId)
	}
	return modelToEntity(&m), nil
}

func (r *repoImpl) UpdateState(ctx context.Context, clusterId uint64, state entity.ClusterEncryptionBindingState, message string) bizerr.BizError {
	_, span := tracer.Start(ctx, "UpdateState")
	defer span.End()

	result := r.provider.WriteDB(ctx).Model(&schema.ClusterEncryptionBinding{}).
		Where("cluster_id = ?", clusterId).
		Select("State", "Message").
		Updates(schema.ClusterEncryptionBinding{
			State:   string(state),
			Message: message,
		})
	if result.Error != nil {
		return bizerr.WrapGormError(result.Error, "failed to update cluster encryption binding state")
	}
	if result.RowsAffected == 0 {
		return bizerr.New(bizerr.ErrCodeRecordNotFound, "cluster encryption binding for cluster %d not found", clusterId)
	}
	return nil
}

func (r *repoImpl) Delete(ctx context.Context, clusterId uint64) bizerr.BizError {
	_, span := tracer.Start(ctx, "Delete")
	defer span.End()

	result := r.provider.WriteDB(ctx).
		Where("cluster_id = ?", clusterId).
		Delete(&schema.ClusterEncryptionBinding{})
	if result.Error != nil {
		return bizerr.WrapGormError(result.Error, "failed to delete cluster encryption binding for cluster %d", clusterId)
	}
	return nil
}

func (r *repoImpl) IsModifying(ctx context.Context, clusterId uint64) (bool, bizerr.BizError) {
	_, span := tracer.Start(ctx, "IsModifying")
	defer span.End()

	var count int64
	if err := r.provider.ReadDB(ctx).Model(&schema.ClusterEncryptionBinding{}).
		Where("cluster_id = ? AND state IN ?", clusterId, []string{
			string(entity.ClusterEncryptionBindingStateModifying),
			string(entity.ClusterEncryptionBindingStatePendingRestore),
		}).
		Count(&count).Error; err != nil {
		return false, bizerr.WrapGormError(err, "failed to check modifying binding")
	}
	return count > 0, nil
}
