package repo

import (
	"context"
	"fmt"

	appent "github.com/tidbcloud/devops/entity/application"
	appmod "github.com/tidbcloud/devops/models/application"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"
	"github.com/tidbcloud/devops/pkg/service/application/repo/cvt"

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

type Driver interface {
	Save(ctx context.Context, app *appent.Application) (*appent.Application, error)
	Load(ctx context.Context, name string) (*appent.Application, error)
	Query(ctx context.Context, opts ...repoopt.Option) ([]*appent.Application, error)
	Count(ctx context.Context, opts ...repoopt.Option) (int64, error)
	Delete(ctx context.Context, name string) error
}

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

func (d *driver) Save(ctx context.Context, app *appent.Application) (*appent.Application, error) {
	wdb := d.dbProvider.WriteDB(ctx)
	mod := d.cvter.EntityToModel().Application(app)

	if app.ID > 0 {
		// update association of owners
		existOwners := app.Owners
		subDB := wdb.Where("application = ?", mod.Name)
		if len(app.Owners) != 0 {
			subDB = subDB.Where("NOT owner IN ?", existOwners)
		}
		err := subDB.Delete(&appmod.ApplicationToOwner{}).Error
		if err != nil {
			return nil, fmt.Errorf("delete owners of application %s failed: %w", app.Name, err)
		}
	}

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

	// fill back id
	return d.cvter.ModelToEntity().Application(mod), nil
}

func (d *driver) Load(ctx context.Context, name string) (*appent.Application, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	mod := &appmod.Application{}

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

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

func (d *driver) Query(ctx context.Context, opts ...repoopt.Option) ([]*appent.Application, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	mods := []*appmod.Application{}

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

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

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

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

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

	return count, nil
}

func (d *driver) Delete(ctx context.Context, name string) error {
	wdb := d.dbProvider.WriteDB(ctx)

	err := wdb.Where("name = ?", name).Delete(&appmod.Application{}).Error
	if err != nil {
		return fmt.Errorf("delete application %s failed: %w", name, err)
	}

	// delete owners
	err = wdb.Where("application = ?", name).Delete(&appmod.ApplicationToOwner{}).Error
	if err != nil {
		return fmt.Errorf("delete owners of application %s failed: %w", name, err)
	}

	return nil
}

func (d *driver) buildModel(db *gorm.DB) *gorm.DB {
	db = db.Model(&appmod.Application{}).
		Preload("Owners")

	return db
}
