package repo

import (
	"context"

	ccent "github.com/tidbcloud/devops/entity/cloudconfig"
	ccmod "github.com/tidbcloud/devops/models/cloudconfig"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"

	"gorm.io/gorm/clause"
)

type DefaultConfigurationChangeDriver interface {
	SaveDefaultConfigurationChange(ctx context.Context, modif *ccent.DefaultConfigurationChange) (*ccent.DefaultConfigurationChange, error)
	LoadDefaultConfigurationChange(ctx context.Context, id uint64) (*ccent.DefaultConfigurationChange, error)
	QueryDefaultConfigurationChanges(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChange, error)
	CountDefaultConfigurationChanges(ctx context.Context, opts ...repoopt.Option) (int64, error)

	LoadDefaultConfigurationChangeRelease(ctx context.Context, id uint64) (*ccent.DefaultConfigurationChangeRelease, error)
	SaveDefaultConfigurationChangeRelease(ctx context.Context, modif *ccent.DefaultConfigurationChangeRelease) (*ccent.DefaultConfigurationChangeRelease, error)
	QueryDefaultConfigurationChangeReleases(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeRelease, error)
	QueryLatestDefaultConfigurationChangeReleasePerEnvironment(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeRelease, error)

	QueryDefaultConfigurationChangeItems(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeItem, error)
	BatchSaveDefaultConfigurationChangeItems(ctx context.Context, changes []*ccent.DefaultConfigurationChangeItem) error
	CountDefaultConfigurationChangeItems(ctx context.Context, opts ...repoopt.Option) (int64, error)
	StatDefaultConfigurationChangeItems(ctx context.Context, result any, opts ...repoopt.Option) error

	QueryDefaultConfigurationChangeItemReleases(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeItemRelease, error)
	BatchInsertDefaultConfigurationChangeItemReleases(ctx context.Context, changes []*ccent.DefaultConfigurationChangeItemRelease) error
	QueryLastestDefaultConfigurationChangeItemReleasesPerItem(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeItemRelease, error)
}

type tcDriver driver

func (d *tcDriver) SaveDefaultConfigurationChange(ctx context.Context, chg *ccent.DefaultConfigurationChange) (*ccent.DefaultConfigurationChange, error) {
	wdb := d.dbProvider.WriteDB(ctx)
	chgMod := d.cvter.EntityToModel().DefaultConfigurationChange(chg)

	err := wdb.Save(chgMod).Error
	if err != nil {
		return nil, err
	}

	// fill back id
	return d.cvter.ModelToEntity().DefaultConfigurationChange(chgMod), nil
}

func (d *tcDriver) LoadDefaultConfigurationChange(ctx context.Context, id uint64) (*ccent.DefaultConfigurationChange, error) {
	rdb := d.dbProvider.ReadDB(ctx).Model(&ccmod.DefaultConfigurationChange{})
	chgMod := &ccmod.DefaultConfigurationChange{}

	err := rdb.Where("id = ?", id).First(chgMod).Error
	if err != nil {
		return nil, err
	}

	return d.cvter.ModelToEntity().DefaultConfigurationChange(chgMod), nil
}
func (d *tcDriver) QueryDefaultConfigurationChanges(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChange, error) {
	rdb := d.dbProvider.ReadDB(ctx).Model(&ccmod.DefaultConfigurationChange{})
	chgMods := make([]*ccmod.DefaultConfigurationChange, 0)

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

	err := rdb.Find(&chgMods).Error
	if err != nil {
		return nil, err
	}

	return container.TransformSlice(chgMods, d.cvter.ModelToEntity().DefaultConfigurationChange), nil
}

func (d *tcDriver) CountDefaultConfigurationChanges(ctx context.Context, opts ...repoopt.Option) (int64, error) {
	rdb := d.dbProvider.ReadDB(ctx).Model(&ccmod.DefaultConfigurationChange{})
	rdb = repoopt.Aggregate(opts...).Apply(rdb)

	var count int64
	err := rdb.Count(&count).Error
	if err != nil {
		return 0, err
	}

	return count, nil
}

func (d *tcDriver) LoadDefaultConfigurationChangeRelease(ctx context.Context, id uint64) (*ccent.DefaultConfigurationChangeRelease, error) {
	rdb := d.dbProvider.ReadDB(ctx).Model(&ccmod.DefaultConfigurationChangeRelease{})

	mod := &ccmod.DefaultConfigurationChangeRelease{}
	err := rdb.Where("id = ?", id).First(mod).Error
	if err != nil {
		return nil, err
	}

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

func (d *tcDriver) SaveDefaultConfigurationChangeRelease(ctx context.Context, chg *ccent.DefaultConfigurationChangeRelease) (*ccent.DefaultConfigurationChangeRelease, error) {
	wdb := d.dbProvider.WriteDB(ctx)
	chgMod := d.cvter.EntityToModel().DefaultConfigurationChangeRelease(chg)

	err := wdb.Save(chgMod).Error
	if err != nil {
		return nil, err
	}

	// fill back id
	return d.cvter.ModelToEntity().DefaultConfigurationChangeRelease(chgMod), nil
}
func (d *tcDriver) QueryDefaultConfigurationChangeReleases(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeRelease, error) {
	rdb := d.dbProvider.ReadDB(ctx).Model(&ccmod.DefaultConfigurationChangeRelease{})
	chgMods := make([]*ccmod.DefaultConfigurationChangeRelease, 0)

	rdb = repoopt.Aggregate(opts...).Apply(rdb).Order("release_number DESC")

	err := rdb.Find(&chgMods).Error
	if err != nil {
		return nil, err
	}

	return container.TransformSlice(chgMods, d.cvter.ModelToEntity().DefaultConfigurationChangeRelease), nil
}

func (d *tcDriver) QueryLatestDefaultConfigurationChangeReleasePerEnvironment(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeRelease, error) {
	type Latest struct {
		Environment   string `gorm:"column:environment"`
		ReleaseNumber uint64 `gorm:"column:release_number"`
	}

	results := []Latest{}
	{
		rdb := d.dbProvider.ReadDB(ctx).Model(&ccmod.DefaultConfigurationChangeRelease{})
		subQ := repoopt.Aggregate(opts...).Apply(rdb.Select("environment, MAX(release_number) as release_number"))
		err := subQ.Group("environment").Scan(&results).Error
		if err != nil {
			return nil, err
		}
	}

	if len(results) == 0 {
		return nil, nil
	}

	subCond := container.TransformSlice(results, func(e Latest) []any {
		return []any{e.Environment, e.ReleaseNumber}
	})
	chgMods := make([]*ccmod.DefaultConfigurationChangeRelease, 0)
	rdb := d.dbProvider.ReadDB(ctx).Model(&ccmod.DefaultConfigurationChangeRelease{})
	err := repoopt.Aggregate(opts...).Apply(rdb).Where("(environment, release_number) IN ?", subCond).Find(&chgMods).Error
	if err != nil {
		return nil, err
	}

	return container.TransformSlice(chgMods, d.cvter.ModelToEntity().DefaultConfigurationChangeRelease), nil
}

func (d *tcDriver) QueryDefaultConfigurationChangeItems(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeItem, error) {
	rdb := d.dbProvider.ReadDB(ctx).Model(&ccmod.DefaultConfigurationChangeItem{})
	chgMods := make([]*ccmod.DefaultConfigurationChangeItem, 0)

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

	err := rdb.Find(&chgMods).Error
	if err != nil {
		return nil, err
	}

	return container.TransformSlice(chgMods, d.cvter.ModelToEntity().DefaultConfigurationChangeItem), nil
}

func (d *tcDriver) BatchSaveDefaultConfigurationChangeItems(ctx context.Context, changes []*ccent.DefaultConfigurationChangeItem) error {
	chgMods := container.TransformSlice(changes, d.cvter.EntityToModel().DefaultConfigurationChangeItem)

	wdb := d.dbProvider.WriteDB(ctx)
	err := wdb.Clauses(clause.OnConflict{UpdateAll: true}).CreateInBatches(chgMods, 30).Error // upsert DefaultConfigurationChangeItemRelease
	if err != nil {
		return err
	}

	return nil
}

func (d *tcDriver) CountDefaultConfigurationChangeItems(ctx context.Context, opts ...repoopt.Option) (int64, error) {
	rdb := d.dbProvider.ReadDB(ctx).Model(&ccmod.DefaultConfigurationChangeItem{})
	rdb = repoopt.Aggregate(opts...).Apply(rdb)

	var count int64
	err := rdb.Count(&count).Error
	if err != nil {
		return 0, err
	}

	return count, nil
}

func (d *tcDriver) StatDefaultConfigurationChangeItems(ctx context.Context, result any, opts ...repoopt.Option) error {
	rdb := d.dbProvider.ReadDB(ctx).Model(&ccmod.DefaultConfigurationChangeItem{})
	rdb = repoopt.Aggregate(opts...).Apply(rdb)

	err := rdb.Scan(result).Error
	if err != nil {
		return err
	}

	return nil
}

func (d *tcDriver) QueryDefaultConfigurationChangeItemReleases(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeItemRelease, error) {
	rdb := d.dbProvider.ReadDB(ctx).Model(&ccmod.DefaultConfigurationChangeItemRelease{})
	chgMods := make([]*ccmod.DefaultConfigurationChangeItemRelease, 0)

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

	err := rdb.Find(&chgMods).Error
	if err != nil {
		return nil, err
	}

	return container.TransformSlice(chgMods, d.cvter.ModelToEntity().DefaultConfigurationChangeItemRelease), nil
}

func (d *tcDriver) QueryLastestDefaultConfigurationChangeItemReleasesPerItem(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeItemRelease, error) {
	type Latest struct {
		ItemID        uint64 `gorm:"column:item_id"`
		ReleaseNumber uint64 `gorm:"column:release_number"`
	}

	results := []Latest{}
	{
		rdb := d.dbProvider.ReadDB(ctx).Model(&ccmod.DefaultConfigurationChangeItemRelease{})
		subQ := repoopt.Aggregate(opts...).Apply(rdb.Select("item_id, MAX(release_number) as release_number"))
		err := subQ.Group("item_id").Scan(&results).Error
		if err != nil {
			return nil, err
		}
	}

	if len(results) == 0 {
		return nil, nil
	}

	subCond := container.TransformSlice(results, func(e Latest) []any {
		return []any{e.ItemID, e.ReleaseNumber}
	})
	chgMods := make([]*ccmod.DefaultConfigurationChangeItemRelease, 0)
	rdb := d.dbProvider.ReadDB(ctx).Model(&ccmod.DefaultConfigurationChangeItemRelease{})
	err := repoopt.Aggregate(opts...).Apply(rdb).Where("(item_id, release_number) IN ?", subCond).Find(&chgMods).Error
	if err != nil {
		return nil, err
	}

	return container.TransformSlice(chgMods, d.cvter.ModelToEntity().DefaultConfigurationChangeItemRelease), nil
}

func (d *tcDriver) BatchInsertDefaultConfigurationChangeItemReleases(ctx context.Context, changes []*ccent.DefaultConfigurationChangeItemRelease) error {
	chgMods := container.TransformSlice(changes, d.cvter.EntityToModel().DefaultConfigurationChangeItemRelease)

	wdb := d.dbProvider.WriteDB(ctx)
	err := wdb.CreateInBatches(chgMods, 30).Error // not allow update
	if err != nil {
		return err
	}

	return nil
}
