package repo

import (
	"context"
	"errors"

	"braces.dev/errtrace"
	"github.com/tidbcloud/pkgv2/db"
	"gorm.io/gorm"

	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/repository"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/infra/persistence/model"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/infra/persistence/repo/cvt"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

type cloudProviderRepoImpl struct {
	dbProvider db.Provider
}

func NewCloudProviderRepo(dbProvider db.Provider) repository.CloudProviderRepo {
	return &cloudProviderRepoImpl{
		dbProvider: dbProvider,
	}
}

func (r *cloudProviderRepoImpl) LoadAll(ctx context.Context) ([]*entity.CloudProvider, error) {
	ctx, span := Tracer.Start(ctx, "cloudProviderRepo.LoadAll")
	defer span.End()

	rdb := r.dbProvider.ReadDB(ctx)
	var providers []*model.Provider
	if err := rdb.Model(&model.Provider{}).Preload("Regions").Preload("Regions.RegionTenants").
		Find(&providers).Error; err != nil {
		return nil, errtrace.Wrap(bizerr.WrapGormError(err, "load all cloud providers"))
	}

	outs := make([]*entity.CloudProvider, 0, len(providers))
	for _, each := range providers {
		c, err := cvt.CloudProvider2En(each)
		if err != nil {
			return nil, errtrace.Wrap(err)
		}
		outs = append(outs, c)
	}
	return outs, nil
}

func (r *cloudProviderRepoImpl) LoadByName(
	ctx context.Context,
	name entity.CloudProviderName,
) (*entity.CloudProvider, error) {
	ctx, span := Tracer.Start(ctx, "cloudProviderRepo.LoadByName")
	defer span.End()

	rdb := r.dbProvider.ReadDB(ctx)
	var provider model.Provider
	if err := rdb.Model(&model.Provider{}).Where("name=?", name.String()).Preload("Regions").Preload("Regions.RegionTenants").
		Take(&provider).Error; err != nil {
		return nil, errtrace.Wrap(bizerr.WrapGormError(err, "cloud provider not found"))
	}

	if provider.ID == 0 {
		return nil, errtrace.Wrap(bizerr.New(bizerr.ErrCodeRecordNotFound, "cloud provider not found"))
	}

	return cvt.CloudProvider2En(&provider)
}

func (r *cloudProviderRepoImpl) Save(
	ctx context.Context,
	provider *entity.CloudProvider,
) (*entity.CloudProvider, error) {
	if provider == nil {
		return nil, errors.New("empty provider")
	}

	ctx, span := Tracer.Start(ctx, "cloudProviderRepo.Save")
	defer span.End()

	providerModel := cvt.CloudProviderEnToModel(provider)

	// find provider by id
	rdb := r.dbProvider.ReadDB(ctx)
	var pr model.Provider
	if err := rdb.Model(&model.Provider{}).
		Where("`providers`.`name` = ?", provider.Id.String()).Take(&pr).Error; err != nil {
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerr.WrapGormError(err, "find cloud provider %s", provider.Id.String())
		}
	} else {
		// update provider id
		providerModel.ID = pr.ID
	}

	wdb := r.dbProvider.WriteDB(ctx)

	if err := wdb.Save(providerModel).Error; err != nil {
		return nil, errtrace.Wrap(err)
	}

	return cvt.CloudProvider2En(providerModel)
}
