//go:generate ${GOBIN}/mockgen -destination repo_mock.go -package=repo ${GO_MODULE}/pkg/service/tenant/repo TenantRepo
package repo

import (
	"context"
	"errors"
	"fmt"

	tntent "github.com/tidbcloud/devops/entity/tenant"
	tntmod "github.com/tidbcloud/devops/models/tenant"
	contutil "github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"
	"github.com/tidbcloud/devops/pkg/service/tenant/repo/cvt"

	"github.com/tidbcloud/pkgv2/db"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type TenantRepo interface {
	Load(ctx context.Context, id uint64) (*tntent.Tenant, error)
	Query(ctx context.Context, opts ...repoopt.Option) ([]*tntent.Tenant, error)
	Count(ctx context.Context, opts ...repoopt.Option) (int64, error)
	Save(ctx context.Context, tenant *tntent.Tenant) error
	BatchSave(ctx context.Context, tenants []*tntent.Tenant) error
	Delete(ctx context.Context, tenant *tntent.Tenant) error
}

type tenantRepo struct {
	cvter      *cvt.Converter
	dbProvider db.Provider
}

func NewTenantRepo(dbProvider db.Provider) TenantRepo {
	return &tenantRepo{
		dbProvider: dbProvider,
		cvter:      cvt.NewConverter(),
	}
}

func (r *tenantRepo) Load(ctx context.Context, id uint64) (*tntent.Tenant, error) {
	rdb := r.dbProvider.ReadDB(ctx)
	rdb = rdb.Model(&tntmod.Tenant{})

	tenant := &tntmod.Tenant{}
	err := rdb.Where("id = ?", id).First(tenant).Error
	if err != nil {
		return nil, fmt.Errorf("load tenant %d failed: %w", id, err)
	}
	return r.cvter.ModelToEntity.Tenant(tenant), nil
}

func (r *tenantRepo) Query(ctx context.Context, opts ...repoopt.Option) ([]*tntent.Tenant, error) {
	rdb := r.dbProvider.ReadDB(ctx)
	rdb = rdb.Model(&tntmod.Tenant{})
	rdb = repoopt.Aggregate(opts...).Apply(rdb)

	var tenants []*tntmod.Tenant
	err := rdb.Find(&tenants).Error
	if err != nil {
		return nil, fmt.Errorf("query tenant failed: %w", err)
	}
	return contutil.TransformSlice(tenants, r.cvter.ModelToEntity.Tenant), nil
}

func (r *tenantRepo) Count(ctx context.Context, opts ...repoopt.Option) (int64, error) {
	rdb := r.dbProvider.ReadDB(ctx)
	rdb = rdb.Model(&tntmod.Tenant{})
	rdb = repoopt.Aggregate(opts...).Apply(rdb)

	var count int64
	err := rdb.Count(&count).Error
	if err != nil {
		return 0, fmt.Errorf("count tenant failed: %w", err)
	}
	return count, nil
}

func (r *tenantRepo) Save(ctx context.Context, tenant *tntent.Tenant) error {
	wdb := r.dbProvider.WriteDB(ctx)
	tenantModel := r.cvter.EntityToModel.Tenant(tenant)

	_, err := r.Load(ctx, tenant.Id)
	if err != nil {
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			return err
		}
		err = wdb.Create(tenantModel).Error
		if err != nil {
			return fmt.Errorf("create tenant %d failed: %w", tenant.Id, err)
		}
		return nil
	}
	err = wdb.Model(tenantModel).Select("*").Updates(tenantModel).Error
	if err != nil {
		return fmt.Errorf("update tenant %d failed: %w", tenant.Id, err)
	}
	return nil
}

func (r *tenantRepo) BatchSave(ctx context.Context, tenants []*tntent.Tenant) error {
	wdb := r.dbProvider.WriteDB(ctx)
	tenantModels := contutil.TransformSlice(tenants, r.cvter.EntityToModel.Tenant)

	err := wdb.Clauses(clause.OnConflict{
		UpdateAll: true,
	}).Create(&tenantModels).Error
	if err != nil {
		return fmt.Errorf("batch save tenants failed: %w", err)
	}
	return nil
}

func (r *tenantRepo) Delete(ctx context.Context, tenant *tntent.Tenant) error {
	wdb := r.dbProvider.WriteDB(ctx)
	tenantModel := r.cvter.EntityToModel.Tenant(tenant)

	err := wdb.Delete(tenantModel).Error
	if err != nil {
		return fmt.Errorf("delete tenant %d failed: %w", tenant.Id, err)
	}
	return nil
}
