package repo

import (
	"context"
	"fmt"

	relent "github.com/tidbcloud/devops/entity/release"
	"github.com/tidbcloud/devops/models/base"
	relmod "github.com/tidbcloud/devops/models/release"
	contutil "github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"

	"gorm.io/gorm"
)

type ChangeDriver interface {
	Load(ctx context.Context, id uint64, detail bool) (*relent.ChangeInRepo, error)
	Save(ctx context.Context, change *relent.ChangeInRepo) (*relent.ChangeInRepo, error)
	Query(ctx context.Context, detail bool, opt ...repoopt.Option) ([]*relent.ChangeInRepo, error)
	Count(ctx context.Context, opts ...repoopt.Option) (int64, error)
}

type changeDriver driver

func (r *changeDriver) Load(ctx context.Context, id uint64, detail bool) (*relent.ChangeInRepo, error) {
	rdb := r.dbProvider.ReadDB(ctx)
	change := &relmod.Change{}

	rdb = buildModel(rdb, detail)

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

	return r.cvter.ModelToEntity.Change(change), nil
}

func (r *changeDriver) Save(ctx context.Context, change *relent.ChangeInRepo) (*relent.ChangeInRepo, error) {
	var err error
	wdb := r.dbProvider.WriteDB(ctx)
	changeMod := r.cvter.EntityToModel.Change(change)

	if change.ID > 0 {
		// delete old deployments
		existDeployIDs := []base.ID{}
		for _, d := range changeMod.Deployments {
			if d.ID != 0 {
				existDeployIDs = append(existDeployIDs, d.ID)
			}
		}

		db := wdb.Where("change_id = ?", change.ID)
		if len(existDeployIDs) != 0 {
			db = db.Where("NOT id IN ?", existDeployIDs)
		}
		err := db.Delete(&relmod.ChangeDeployment{}).Error
		if err != nil {
			return nil, fmt.Errorf("delete deployments of change %d failed: %w", change.ID, err)
		}
	}

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

	// if change.ID > 0 {
	// 	err = wdb.Association("Detail").Replace(changeMod.Detail)
	// 	if err != nil {
	// 		return nil, fmt.Errorf("update detail of change %d failed: %w", change.ID, err)
	// 	}
	// 	err = wdb.Association("Deployments").Replace(changeMod.Deployments)
	// 	if err != nil {
	// 		return nil, fmt.Errorf("update deployments of change %d failed: %w", change.ID, err)
	// 	}
	// 	err = wdb.Association("Notification").Replace(changeMod.Notification)
	// 	if err != nil {
	// 		return nil, fmt.Errorf("update deployments of change %d failed: %w", change.ID, err)
	// 	}
	// 	err = wdb.Save(changeMod).Error
	// 	if err != nil {
	// 		return nil, fmt.Errorf("update change %d failed: %w", change.ID, err)
	// 	}
	// } else {
	// 	err = wdb.Create(changeMod).Error
	// 	if err != nil {
	// 		return nil, fmt.Errorf("create change failed: %w", err)
	// 	}
	// }

	return r.cvter.ModelToEntity.Change(changeMod), nil
}

func (r *changeDriver) Query(ctx context.Context, detail bool, opts ...repoopt.Option) ([]*relent.ChangeInRepo, error) {
	rdb := r.dbProvider.ReadDB(ctx)
	changes := []*relmod.Change{}

	rdb = buildModel(rdb, detail)

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

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

	return contutil.TransformSlice(changes, r.cvter.ModelToEntity.Change), nil
}

func (r *changeDriver) Count(ctx context.Context, opts ...repoopt.Option) (int64, error) {
	db := r.dbProvider.ReadDB(ctx).Model(&relmod.Change{})
	db = repoopt.Aggregate(opts...).Apply(db)

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

	return count, nil
}

func buildModel(db *gorm.DB, detail bool) *gorm.DB {
	db = db.Model(&relmod.Change{}).Preload("Deployments").Preload("Notification")
	if detail {
		db = db.Preload("Detail")
	}

	return db
}
