package encryptionkey

import (
	"context"

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

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/encryptionkey"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

var tracer = otel.Tracer("encryptionkey_repo_impl")

type RepoImpl struct {
	provider db.Provider
}

func NewEncryptionKeyRepo(provider db.Provider) encryptionkey.Repo {
	return &RepoImpl{
		provider: provider,
	}
}

func (r RepoImpl) Load(ctx context.Context, id uint64, showDeleted bool) (*entity.EncryptionKey, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "EncryptionKeyRepo.Load")
	defer span.End()

	if id == 0 {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "encryption key id is empty")
	}

	var encryptionKey schema.EncryptionKey
	rdb := r.provider.ReadDB(ctx)

	if showDeleted {
		rdb = rdb.Unscoped()
	}

	rdb = rdb.Model(&schema.EncryptionKey{}).InnerJoins("Provider").InnerJoins("Region").
		Where("`encryption_keys`.`id` = ?", id)

	if err := rdb.Take(&encryptionKey).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to get encryption key")
	}

	return modelToEntity(&encryptionKey)
}

func (r RepoImpl) Create(ctx context.Context, encryptionKey *entity.EncryptionKey) bizerr.BizError {
	ctx, span := tracer.Start(ctx, "EncryptionKeyRepo.Create")
	defer span.End()

	if encryptionKey == nil {
		return bizerr.New(bizerr.ErrCodeParams, "encryption key is nil")
	}

	wdb := r.provider.WriteDB(ctx)

	var providerModel schema.Provider
	if err := wdb.Take(&providerModel, "id = ?", encryptionKey.ProviderId).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to query provider")
	}
	var regionModel schema.Region
	if err := wdb.Take(&regionModel, "id = ?", encryptionKey.RegionId).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to query region")
	}

	model, err := entityToModel(encryptionKey)
	if err != nil {
		return bizerr.WrapError(err, "convert encryption key entity to model")
	}

	dbErr := wdb.Create(model).Error
	if dbErr != nil {
		return bizerr.WrapGormError(dbErr, "failed to save encryption key")
	}

	return nil
}

func (r RepoImpl) GetDefaultEncryptionKey(ctx context.Context,
	tenantId uint64,
	regionId uint64,
	providerIdentityId uint64) (*entity.EncryptionKey, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "EncryptionKeyRepo.GetDefaultEncryptionKey")
	defer span.End()

	if tenantId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "tenantId is required")
	}
	if regionId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "regionId is required")
	}
	if providerIdentityId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "providerIdentityId is required")
	}

	rdb := r.provider.ReadDB(ctx).Model(&schema.EncryptionKey{}).InnerJoins("Provider").InnerJoins("Region")

	rdb = rdb.Where("encryption_keys.tenant_id = ?", tenantId).
		Where("encryption_keys.region_id = ?", regionId).
		Where("encryption_keys.provider_identity_id = ?", providerIdentityId).
		Where("encryption_keys.customer_managed = ?", false)

	var model schema.EncryptionKey
	if err := rdb.Take(&model).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to get default encryption key")
	}

	return modelToEntity(&model)
}

func (r RepoImpl) ListEncryptionKeys(ctx context.Context,
	filter *encryptionkey.Filter) ([]*entity.EncryptionKey, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "EncryptionKeyRepo.ListEncryptionKeys")
	defer span.End()

	rdb := r.provider.ReadDB(ctx).Model(&schema.EncryptionKey{}).InnerJoins("Provider").InnerJoins("Region")

	if len(filter.Ids) > 0 {
		rdb = rdb.Where("encryption_keys.id IN ?", filter.Ids)
	}
	if filter.TenantId != 0 {
		rdb = rdb.Where("encryption_keys.tenant_id = ?", filter.TenantId)
	}
	if filter.ProviderName != globalcommonv1.CloudProvider_NAME_UNSPECIFIED {
		rdb = rdb.Where("`Provider`.`name` = ?", filter.ProviderName.String())
	}
	if filter.RegionName != globalcommonv1.Region_NAME_UNSPECIFIED {
		rdb = rdb.Where("`Region`.`region_name` = ?", filter.RegionName.String())
	}
	if filter.ServicePlan != globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
		rdb = rdb.Where("encryption_keys.service_plan = ?", filter.ServicePlan.String())
	}
	if len(filter.ProviderIdentityId) > 0 {
		rdb = rdb.Where("encryption_keys.provider_identity_id IN ?", filter.ProviderIdentityId)
	}
	if filter.CustomerManaged != nil {
		rdb = rdb.Where("encryption_keys.customer_managed = ?", *filter.CustomerManaged)
	}

	var models []*schema.EncryptionKey
	if err := rdb.Find(&models).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to list encryption keys")
	}

	var entities []*entity.EncryptionKey
	for _, model := range models {
		en, err := modelToEntity(model)
		if err != nil {
			return nil, bizerr.WrapError(err, "convert encryption key model to entity")
		}
		entities = append(entities, en)
	}

	return entities, nil
}

func (r RepoImpl) FindByMasterKeyAndTenant(
	ctx context.Context,
	masterKey string,
	tenantId, regionId uint64,
) (*entity.EncryptionKey, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "EncryptionKeyRepo.FindByMasterKeyAndTenant")
	defer span.End()

	if masterKey == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "masterKey is required")
	}
	if tenantId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "tenantId is required")
	}
	if regionId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "regionId is required")
	}

	rdb := r.provider.ReadDB(ctx).Model(&schema.EncryptionKey{}).InnerJoins("Provider").InnerJoins("Region")

	rdb = rdb.Where("encryption_keys.master_key = ?", masterKey).
		Where("encryption_keys.tenant_id = ?", tenantId).
		Where("encryption_keys.region_id = ?", regionId)

	var model schema.EncryptionKey
	if err := rdb.Take(&model).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to find encryption key by master key and tenant")
	}
	return modelToEntity(&model)
}

func (r RepoImpl) UpdateMasterKey(ctx context.Context, id uint64, masterKey string) bizerr.BizError {
	ctx, span := tracer.Start(ctx, "EncryptionKeyRepo.UpdateMasterKey")
	defer span.End()

	if id == 0 {
		return bizerr.New(bizerr.ErrCodeParams, "encryption key id is required")
	}

	wdb := r.provider.WriteDB(ctx)
	err := wdb.Model(&schema.EncryptionKey{ID: id}).Update("master_key", masterKey).Error
	if err != nil {
		return bizerr.WrapGormError(err, "failed to update encryption key master key")
	}
	return nil
}
