package repo

import (
	"fmt"
	"strings"

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

	"gorm.io/gorm"
)

var (
	InstanceOpts iOpts
)

type iOpts struct{}

func (_ iOpts) IDNotIn(ids []uint64) repoopt.Option {
	return &repoopt.Filter[uint64]{
		Column:    "id",
		ValuesNot: ids,
	}
}

func (_ iOpts) TargetIDIn(typ opent.OperationTarget, tids []string) repoopt.Option {
	// SELECT instance_id FROM operation_instance_to_cluster WHERE cluster_id in (...)
	subQuery := &repoopt.Select{
		Module: &opmod.OperationInstanceToTargetMapping{},
		Select: []string{"instance_id"},
		Filter: repoopt.Aggregate(
			&repoopt.Filter[string]{
				Column: "target_type",
				Value:  pointer.Pointer(string(typ)),
			},
			&repoopt.Filter[string]{
				Column: "target_id",
				Values: tids,
			},
		),
	}

	return &repoopt.FilterWithSubQuery{
		Column:   "id",
		SubQuery: subQuery,
		Values:   true,
	}
}

func (_ iOpts) TypeIs(typ opent.OperationTarget) repoopt.Option {
	return &repoopt.Filter[opent.OperationTarget]{
		Column: "type",
		Value:  &typ,
	}
}

func (_ iOpts) CreateBefore(t int64) repoopt.Option {
	return &repoopt.Raw{
		RawApply: func(db *gorm.DB) *gorm.DB {
			return db.Where("created_at <= FROM_UNIXTIME(?)", t)
		},
	}
}

func (_ iOpts) CreateAfter(t int64) repoopt.Option {
	return &repoopt.Raw{
		RawApply: func(db *gorm.DB) *gorm.DB {
			return db.Where("created_at >= FROM_UNIXTIME(?)", t)
		},
	}
}

func (_ iOpts) WithPager(page int, perPage int) repoopt.Option {
	pager := &repoopt.Pager{
		Page:    page,
		PerPage: perPage,
	}

	order := &repoopt.Order{
		Column: "created_at",
		Desc:   true,
	}
	return repoopt.Aggregate(pager, order)
}

func (_ iOpts) PhaseIn(phases []opent.InstancePhase) repoopt.Option {
	return &repoopt.Filter[opent.InstancePhase]{
		Column: "phase",
		Values: phases,
	}
}

func (_ iOpts) PhaseNotIn(phases []opent.InstancePhase) repoopt.Option {
	return &repoopt.Filter[opent.InstancePhase]{
		Column:    "phase",
		ValuesNot: phases,
	}
}

func (_ iOpts) ApplicantIn(applicants []string) repoopt.Option {
	return &repoopt.Filter[string]{
		Column: "applicant",
		Values: applicants,
	}
}

func (_ iOpts) OperationIn(operations []string) repoopt.Option {
	return &repoopt.Filter[string]{
		Column: "operation",
		Values: operations,
	}
}

func (_ iOpts) ApprovalIs(apprID uint64) repoopt.Option {
	return &repoopt.Filter[uint64]{
		Column: "approval_id",
		Value:  &apprID,
	}
}

func (_ iOpts) Search(search string) repoopt.Option {
	return &InstanceSearch{
		Search: search,
	}
}

func (_ iOpts) ParamsLike(params map[opent.ParamName]string) repoopt.Option {
	return &InstanceParamsLike{
		Params: params,
	}
}

type InstanceSearch struct {
	Search string
}

func (t *InstanceSearch) Apply(db *gorm.DB) *gorm.DB {
	if t == nil {
		return db
	}
	if len(t.Search) > 0 {
		db = db.Where("lower(operation) LIKE ? or lower(applicant) LIKE ?",
			"%"+strings.ToLower(t.Search)+"%",
			"%"+strings.ToLower(t.Search)+"%",
		)
	}
	return db
}

type InstanceParamsLike struct {
	Params map[opent.ParamName]string
}

func (t *InstanceParamsLike) Apply(db *gorm.DB) *gorm.DB {
	if t == nil {
		return db
	}
	for name, value := range t.Params {
		if len(name) == 0 || len(value) == 0 {
			continue
		}
		db = db.Where("params LIKE ?", fmt.Sprintf("%%\"%s\": \"%s\"%%", name, value))
	}
	return db
}
