package cmek

import (
	"context"

	"github.com/samber/lo"
	"github.com/tidbcloud/pkgv2/db"

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

type customerManagedEncryptionKeyRepoImpl struct {
	provider db.Provider
}

func NewRepo(provider db.Provider) cmek.Repo {
	return &customerManagedEncryptionKeyRepoImpl{
		provider: provider,
	}
}

func (r *customerManagedEncryptionKeyRepoImpl) Save(
	ctx context.Context,
	en *entity.CustomerManagedEncryptionKey,
) bizerr.BizError {
	wdb := r.provider.WriteDB(ctx)

	regionRdb := r.provider.ReadDB(ctx)
	// Query IDs of associated models.
	var regionModels []*schema.Region
	if err := regionRdb.Model(&schema.Region{}).
		Preload("RegionTenants").
		Where("region_name = ?", en.RegionId.String()).
		Find(&regionModels).
		Error; err != nil {
		return bizerr.WrapGormError(err, "failed to query region")
	}
	if len(regionModels) == 0 {
		return bizerr.New(bizerr.ErrCodeParams, "region %s is not supported", en.RegionId.String())
	}
	region, err := schema.SelectRegionByOrgId(ctx, regionModels, en.OrganizationId)
	if err != nil {
		return err
	}

	var providerModel schema.Provider
	if err := wdb.Take(&providerModel, "name = ?", en.CloudProviderId.String()).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to save cmek")
	}

	cmekModel, err := cvtCmekModel(en, uint64(region.ID), uint64(providerModel.ID))
	if err != nil {
		return bizerr.WrapGormError(err, "failed to save cmek")
	}

	if err := wdb.Create(cmekModel).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to save cmek")
	}

	return nil
}

func (r *customerManagedEncryptionKeyRepoImpl) Get(
	ctx context.Context,
	cmekId uint64,
) (*entity.CustomerManagedEncryptionKey, bizerr.BizError) {
	rdb := r.provider.ReadDB(ctx)

	var cmekModel schema.CustomerManagedEncryptionKey
	if err := rdb.
		InnerJoins("Provider").
		InnerJoins("Region").
		Take(&cmekModel, "`customer_managed_encryption_keys`.`id` = ?", cmekId).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to get cmek")
	}

	cmekEn, err := cvtCmekEntity(&cmekModel)
	if err != nil {
		return nil, err
	}

	return cmekEn, nil
}

func (r *customerManagedEncryptionKeyRepoImpl) Query(
	ctx context.Context,
	filter *cmek.Filter,
) ([]*entity.CustomerManagedEncryptionKey, bizerr.BizError) {
	rdb := r.provider.ReadDB(ctx)
	if filter != nil {
		if len(filter.CloudProviderNames) > 0 {
			cloudProviders := lo.Map(
				filter.CloudProviderNames,
				func(cloudProvider entity.CloudProviderName, index int) string {
					return cloudProvider.String()
				},
			)
			rdb = rdb.Where("`Provider`.`name` IN ?", cloudProviders)
		}
		if len(filter.RegionNames) > 0 {
			rdb = rdb.Where(
				"`Region`.`region_name` IN ?",
				lo.Map(filter.RegionNames, func(region entity.RegionName, index int) string {
					return region.String()
				}),
			)
		}
		if len(filter.OrganizationIds) > 0 {
			rdb = rdb.Where("`customer_managed_encryption_keys`.`tenant_id` in ?", filter.OrganizationIds)
		}

		if len(filter.ProjectIds) > 0 {
			rdb = rdb.Where("`customer_managed_encryption_keys`.`project_id` in ?", filter.ProjectIds)
		}
	}

	var cmeks []*schema.CustomerManagedEncryptionKey
	if err := rdb.InnerJoins("Provider").InnerJoins("Region").Find(&cmeks).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to query cmek")
	}
	var result []*entity.CustomerManagedEncryptionKey
	for _, cmekModel := range cmeks {
		cmekEn, err := cvtCmekEntity(cmekModel)
		if err != nil {
			return nil, err
		}
		result = append(result, cmekEn)
	}

	return result, nil
}

func (r *customerManagedEncryptionKeyRepoImpl) Delete(ctx context.Context, cmekId uint64) bizerr.BizError {
	wdb := r.provider.WriteDB(ctx)

	if err := wdb.Where("`customer_managed_encryption_keys`.`id` = ?", cmekId).
		Delete(&schema.CustomerManagedEncryptionKey{}, cmekId).
		Error; err != nil {
		return bizerr.WrapGormError(err, "failed to delete cmek")
	}

	return nil
}

func (r *customerManagedEncryptionKeyRepoImpl) GetProjectCmekKeySuffix(
	ctx context.Context,
	projectId uint64,
) (*entity.ProjectCmekKeySuffix, bizerr.BizError) {
	rdb := r.provider.ReadDB(ctx)

	var cmekSuffix schema.ProjectCmekKeySuffix
	if err := rdb.Where("project_id = ?", projectId).Take(&cmekSuffix).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to get ProjectCmekKeySuffix")
	}

	return &entity.ProjectCmekKeySuffix{
		Id:        uint64(cmekSuffix.ID),
		ProjectId: cmekSuffix.ProjectId,
		Salt:      cmekSuffix.Salt,
		Suffix:    cmekSuffix.Suffix,
	}, nil
}

func (r *customerManagedEncryptionKeyRepoImpl) SaveProjectCmekKeySuffix(
	ctx context.Context,
	cmekEn *entity.ProjectCmekKeySuffix,
) bizerr.BizError {
	wdb := r.provider.WriteDB(ctx)

	cmekModel := &schema.ProjectCmekKeySuffix{
		ProjectId: cmekEn.ProjectId,
		Salt:      cmekEn.Salt,
		Suffix:    cmekEn.Suffix,
	}

	if err := wdb.Save(cmekModel).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to save ProjectCmekKeySuffix")
	}

	return nil
}
