package repo

import (
	"context"
	"fmt"

	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/models/base"
	opmod "github.com/tidbcloud/devops/models/operation"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"

	"gorm.io/gorm"
)

type InstanceDriver interface {
	Save(ctx context.Context, instance *opent.Instance) (*opent.Instance, error)
	Load(ctx context.Context, id uint64) (*opent.Instance, error)
	Query(ctx context.Context, opts ...repoopt.Option) ([]*opent.Instance, error)
	Count(ctx context.Context, opts ...repoopt.Option) (int64, error)
}

type instanceDriver driver

func (d *instanceDriver) Save(ctx context.Context, instance *opent.Instance) (*opent.Instance, error) {
	wdb := d.dbProvider.WriteDB(ctx)
	instanceMod := d.cvter.EntityToModel().Instance(instance)

	if instance.ID > 0 {
		// update association of actions
		existActionIDs := []base.ID{}
		for _, a := range instanceMod.Actions {
			if a.ID != 0 {
				existActionIDs = append(existActionIDs, a.ID)
			}
		}
		db := wdb.Where("instance_id = ?", instance.ID)
		if len(existActionIDs) != 0 {
			db.Where("NOT id IN ?", existActionIDs)
		}
		err := db.Delete(&opmod.OperationInstanceAction{}).Error
		if err != nil {
			return nil, fmt.Errorf("delete actions of instance %d failed: %w", instance.ID, err)
		}

		if instanceMod.ToTarget != nil {
			// update association of OperationInstanceToCluster
			err = wdb.Where("instance_id = ? AND target_type != ? AND target_id != ? ", instanceMod.ToTarget.InstanceID, instanceMod.ToTarget.TargetType, instanceMod.ToTarget.TargetID).
				Delete(&opmod.OperationInstanceToTargetMapping{}).
				Error
			if err != nil {
				return nil, fmt.Errorf("delete cluster association of instance %d failed: %w", instance.ID, err)
			}
		}
	}

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

	// fill back id
	return d.cvter.ModelToEntity().Instance(instanceMod), nil
}

func (d *instanceDriver) Load(ctx context.Context, id uint64) (*opent.Instance, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	instanceMod := &opmod.OperationInstance{}

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

	return d.cvter.ModelToEntity().Instance(instanceMod), nil
}

func (d *instanceDriver) Query(ctx context.Context, opts ...repoopt.Option) ([]*opent.Instance, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	instanceMods := make([]*opmod.OperationInstance, 0)

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

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

	return container.TransformSlice(instanceMods, d.cvter.ModelToEntity().Instance), nil
}

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

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

	return count, nil
}

func (d *instanceDriver) buildModel(db *gorm.DB) *gorm.DB {
	db = db.Model(&opmod.OperationInstance{}).
		Preload("Actions").Preload("ToTarget")

	return db
}
