package cidr

import (
	"context"

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

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

type cidrRepoImp struct {
	dbProvider db.Provider
}

func NewCIDRRepo(dbProvider db.Provider) cidrrepo.Repo {
	return &cidrRepoImp{dbProvider: dbProvider}
}

func (p *cidrRepoImp) Create(ctx context.Context, en *entity.CIDR) bizerr.BizError {
	wdb := p.dbProvider.WriteDB(ctx)
	region, err := p.loadRegionByNameAndProviderName(
		ctx,
		en.BasicRegion.Name,
		en.BasicRegion.CloudProviderName,
		en.TenantID,
	)
	if err != nil {
		return err
	}

	cidr := cidrEnToModel(en, region)
	if rawErr := wdb.Create(cidr).Error; rawErr != nil {
		return bizerr.WrapGormError(rawErr, "create cidr")
	}
	return nil
}

func (p *cidrRepoImp) Save(ctx context.Context, en *entity.CIDR) bizerr.BizError {
	wdb := p.dbProvider.WriteDB(ctx)

	region, err := p.loadRegionByNameAndProviderName(
		ctx,
		en.BasicRegion.Name,
		en.BasicRegion.CloudProviderName,
		en.TenantID,
	)
	if err != nil {
		return err
	}

	cidr := cidrEnToModel(en, region)
	if rawErr := wdb.Omit("created_at").Save(cidr).Error; rawErr != nil {
		return bizerr.WrapGormError(rawErr, "save cidr")
	}
	return nil
}

func (p *cidrRepoImp) Delete(ctx context.Context, id uint64) bizerr.BizError {
	wdb := p.dbProvider.WriteDB(ctx)
	if err := wdb.Unscoped().Delete(&schema.Cidr{}, id).Error; err != nil {
		return bizerr.WrapGormError(err, "delete cidr")
	}
	return nil
}

func buildCIDRGetter(tx *gorm.DB) *gorm.DB {
	return tx.Model(&schema.Cidr{}).InnerJoins("Region").InnerJoins("Provider")
}

func buildTxByCIDRFilter(tx *gorm.DB, filter *cidrrepo.Filter) *gorm.DB {
	if filter == nil {
		return tx
	}
	if len(filter.IDs) != 0 {
		tx = tx.Where("`cidrs`.`id` IN (?)", filter.IDs)
	}
	if len(filter.ProjectIDs) != 0 {
		tx = tx.Where("`cidrs`.`project_id` IN (?)", filter.ProjectIDs)
	}
	if len(filter.RegionNames) != 0 {
		tx = tx.Where(
			"`Region`.`region_name` IN (?)",
			lo.Map(filter.RegionNames, func(i globalcommonv1.Region_Name, _ int) string { return i.String() }),
		)
	}
	if len(filter.CloudProviderNames) != 0 {
		tx = tx.Where(
			"`Provider`.`name` IN (?)",
			lo.Map(
				filter.CloudProviderNames,
				func(i globalcommonv1.CloudProvider_Name, _ int) string { return i.String() },
			),
		)
	}
	if len(filter.OrganizationIds) != 0 {
		tx = tx.Where("`cidrs`.`tenant_id` IN (?)", filter.OrganizationIds)
	}
	return tx
}

func (p *cidrRepoImp) Query(
	ctx context.Context,
	filter *cidrrepo.Filter,
	sorters []*globaldedicatedv1.Sorter,
) ([]*entity.CIDR, bizerr.BizError) {
	rdb := p.dbProvider.ReadDB(ctx)
	tx := buildTxByCIDRFilter(buildCIDRGetter(rdb), filter)
	tx = common.SetOrderByIntoTx(tx, sorters)

	var cidrList []*schema.Cidr
	if err := tx.Preload("Region").Preload("Provider").Find(&cidrList).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "find cidr")
	}

	var out []*entity.CIDR
	for _, each := range cidrList {
		cidrEntity, err := cidrModelToEn(each)
		if err != nil {
			return nil, err
		}
		out = append(out, cidrEntity)
	}
	return out, nil
}

func (p *cidrRepoImp) Count(ctx context.Context, filter *cidrrepo.Filter) (int64, bizerr.BizError) {
	rdb := p.dbProvider.ReadDB(ctx)
	var count int64
	if err := buildTxByCIDRFilter(buildCIDRGetter(rdb), filter).Count(&count).Error; err != nil {
		return 0, bizerr.WrapGormError(err, "count cidr")
	}
	return count, nil
}

func (p *cidrRepoImp) Load(ctx context.Context, projectID, id uint64) (*entity.CIDR, bizerr.BizError) {
	rdb := p.dbProvider.ReadDB(ctx)
	var cidr schema.Cidr
	if err := buildCIDRGetter(rdb).
		Where("`cidrs`.`project_id` = ? AND `cidrs`.`id` = ?", projectID, id).
		First(&cidr).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load cidr")
	}

	return cidrModelToEn(&cidr)
}

func (p *cidrRepoImp) LoadByCloudProviderAndRegion(
	ctx context.Context,
	projectId, orgId uint64,
	cloudProviderName entity.CloudProviderName,
	regionName entity.RegionName,
) (*entity.CIDR, bizerr.BizError) {
	rdb := p.dbProvider.ReadDB(ctx)

	region, err := p.loadRegionByNameAndProviderName(ctx, regionName, cloudProviderName, orgId)
	if err != nil {
		return nil, err
	}

	var cidr schema.Cidr
	if rawErr := buildCIDRGetter(rdb).
		Where("`cidrs`.`project_id` = ? AND `cidrs`.`region_id` = ?", projectId, region.ID).
		First(&cidr).Error; rawErr != nil {
		return nil, bizerr.WrapGormError(rawErr, "load cidr")
	}
	return cidrModelToEn(&cidr)
}

func (p *cidrRepoImp) loadRegionByNameAndProviderName(
	ctx context.Context,
	regionName entity.RegionName,
	cloudProviderName entity.CloudProviderName,
	orgId uint64,
) (*schema.Region, bizerr.BizError) {
	rdb := p.dbProvider.ReadDB(ctx)
	var regions []*schema.Region
	if err := rdb.Model(&schema.Region{}).InnerJoins("Provider").Preload("RegionTenants").
		Where("`regions`.`region_name` = ? AND `Provider`.`name` = ?", regionName.String(), cloudProviderName.String()).
		Find(&regions).Error; err != nil {
		return nil, bizerr.WrapGormError(
			err,
			"load region %s in cloud provider %s",
			regionName.String(),
			cloudProviderName.String(),
		)
	}
	return schema.SelectRegionByOrgId(ctx, regions, orgId)
}
