package repo

import (
	"context"
	"fmt"

	privent "github.com/tidbcloud/devops/entity/privilege"
	"github.com/tidbcloud/devops/models/base"
	privmod "github.com/tidbcloud/devops/models/privilege"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"
	"github.com/tidbcloud/devops/pkg/service/privilege/repo/cvt"
	"gorm.io/gorm"

	"github.com/tidbcloud/pkgv2/db"
)

type Driver interface {
	Save(ctx context.Context, priv *privent.Privilege) (*privent.Privilege, error)
	Load(ctx context.Context, id uint64) (*privent.Privilege, error)
	Query(ctx context.Context, opts ...repoopt.Option) ([]*privent.Privilege, error)
	Count(ctx context.Context, opts ...repoopt.Option) (int64, error)
}

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

func (d *driver) Save(ctx context.Context, priv *privent.Privilege) (*privent.Privilege, error) {
	wdb := d.dbProvider.WriteDB(ctx)
	mod := d.cvter.EntityToModel().Privilege(priv)

	if priv.ID > 0 {
		// update association of Privilege
		{
			exitIDs := []base.ID{}
			for _, b := range mod.Bindings {
				if b.ID != 0 {
					exitIDs = append(exitIDs, b.ID)
				}
			}
			subDB := wdb.Where("privilege_id = ?", priv.ID)
			if len(exitIDs) != 0 {
				subDB = subDB.Where("NOT id IN ?", exitIDs)
			}
			err := subDB.Delete(&privmod.PrivilegeBinding{}).Error
			if err != nil {
				return nil, fmt.Errorf("delete bindings of privilege %d failed: %w", priv.ID, err)
			}
		}
	}

	err := wdb.Session(&gorm.Session{FullSaveAssociations: true}).Save(mod).Error
	if err != nil {
		return nil, fmt.Errorf("save authorization failed: %w", err)
	}

	return d.cvter.ModelToEntity().Privilege(mod), nil
}

func (d *driver) Load(ctx context.Context, id uint64) (*privent.Privilege, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	mod := &privmod.Privilege{}

	err := d.buildModel(rdb).Where("id = ?", id).First(mod).Error
	if err != nil {
		return nil, fmt.Errorf("load privilege %d failed: %w", id, err)
	}

	return d.cvter.ModelToEntity().Privilege(mod), nil
}

func (d *driver) Query(ctx context.Context, opts ...repoopt.Option) ([]*privent.Privilege, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	mods := make([]*privmod.Privilege, 0)

	rdb = d.buildModel(rdb)
	rdb = repoopt.Aggregate(opts...).Apply(rdb)

	err := rdb.Find(&mods).Error
	if err != nil {
		return nil, fmt.Errorf("query privileges failed: %w", err)
	}

	return container.TransformSlice(mods, d.cvter.ModelToEntity().Privilege), nil
}

func (d *driver) Count(ctx context.Context, opts ...repoopt.Option) (int64, error) {
	rdb := d.dbProvider.ReadDB(ctx).Model(&privmod.Privilege{})
	rdb = repoopt.Aggregate(opts...).Apply(rdb)

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

	return count, nil
}

func (d *driver) buildModel(db *gorm.DB) *gorm.DB {
	db = db.Model(&privmod.Privilege{}).Preload("Bindings")

	return db
}
