package repo

import (
	"fmt"

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

	"gorm.io/gorm"
)

var (
	ConfigurationChangeOpts cccOpts
	// DefaultConfigurationChange Options
	DefaultConfigurationChangeOpts dccOpts
	// DefaultConfigurationChangeReleaseOpts
	DefaultConfigurationChangeReleaseOpts dccrOpts
	// DefaultConfigurationChangeItem Options
	DefaultConfigurationChangeItemsOpts dcctOpts
	// DefaultConfigurationChangeItemRelease Options
	DefaultConfigurationChangeItemReleaseOpts dcctrOpts
)

type cccOpts struct{}

// SelectIDOnly only select id, be same as `select id from <table> ...`
func (_ cccOpts) SelectIDOnly() repoopt.Option {
	return &repoopt.Select{
		Select: []string{"id"},
	}
}

func (_ cccOpts) TargetClusterIs(loc ccent.ClusterConfigurationLocator) repoopt.Option {
	subQuery := &repoopt.Select{
		Module: &ccmod.ConfigurationChangeToCluster{},
		Select: []string{"change_id"},

		Filter: repoopt.Aggregate(
			&repoopt.Filter[string]{
				Column: "cluster_id",
				Value:  pointer.Pointer(loc.ClusterID),
			},
			&repoopt.Filter[ccent.ClusterType]{
				Column: "cluster_type",
				Value:  pointer.Pointer(loc.ClusterType),
			},
		),
	}

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

func (_ cccOpts) TargetTenantInitIs(loc ccent.TenantInitConfigurationLocator) repoopt.Option {
	subQuery := &repoopt.Select{
		Module: &ccmod.ConfigurationChangeToTenantInit{},
		Select: []string{"change_id"},

		Filter: repoopt.Aggregate(
			&repoopt.Filter[uint64]{
				Column: "tenant_id",
				Value:  pointer.Pointer(loc.TenantID),
			},
			&repoopt.Filter[ccent.ClusterType]{
				Column: "cluster_type",
				Value:  pointer.Pointer(loc.ClusterType),
			},
		),
	}

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

func (_ cccOpts) TargetTemplateIs(loc ccent.ConfigurationTemplateLocator) repoopt.Option {
	subQuery := &repoopt.Select{
		Module: &ccmod.ConfigurationChangeToTemplate{},
		Select: []string{"change_id"},

		Filter: repoopt.Aggregate(
			&repoopt.Filter[ccent.ClusterType]{
				Column: "cluster_type",
				Value:  pointer.Pointer(loc.ClusterType),
			},
			&repoopt.Filter[ccent.TemplateType]{
				Column: "template_type",
				Value:  pointer.Pointer(loc.TemplateType),
			},
			&repoopt.Filter[string]{
				Column: "tenant_id",
				Value:  loc.TenantID,
			},
		),
	}

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

// ContainParameterLike search changes that have modified a parameter.
func (_ cccOpts) HaveModifiedParameterLike(param string) repoopt.Option {
	subQuery := &repoopt.Select{
		Module: &ccmod.ConfigurationFileChange{},
		Select: []string{"DISTINCT change_id"},

		Filter: repoopt.Aggregate(
			&repoopt.Filter[string]{
				Column: "JSON_EXTRACT(modified_parameters, '$[*].name')",
				Like:   pointer.Pointer("%%" + param + "%%"),
			},
		),
	}

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

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

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

func (_ cccOpts) 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 (_ cccOpts) PhaseIn(phases []ccent.ConfigurationChangePhase) repoopt.Option {
	return &repoopt.Filter[ccent.ConfigurationChangePhase]{
		Column: "phase",
		Values: phases,
	}
}

func (_ cccOpts) PhaseNotIn(phases []ccent.ConfigurationChangePhase) repoopt.Option {
	return &repoopt.Filter[ccent.ConfigurationChangePhase]{
		Column:    "phase",
		ValuesNot: phases,
	}
}

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

func (_ cccOpts) PremiumUpgradeStepIs(stepID uint64) repoopt.Option {
	return &repoopt.Filter[uint64]{
		Column: "premium_upgrade_step_id",
		Value:  &stepID,
	}
}

type dccOpts struct{}

func (_ dccOpts) PhaseNotIn(phases []ccent.DefaultConfigurationChangePhase) repoopt.Option {
	return &repoopt.Filter[ccent.DefaultConfigurationChangePhase]{
		Column:    "phase",
		ValuesNot: phases,
	}
}

func (_ dccOpts) 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 (_ dccOpts) ContainItemType(typ ccent.DefaultConfigurationChangeItemType) repoopt.Option {
	return &repoopt.Filter[int]{
		Column: fmt.Sprintf("FIND_IN_SET('%s', item_types)", typ),
		Large:  pointer.Pointer(0),
	}
}

type dccrOpts struct{}

func (_ dccrOpts) OwnByChange(changeID uint64) repoopt.Option {
	return &repoopt.Filter[uint64]{
		Column: "parent_id",
		Value:  &changeID,
	}
}

func (_ dccrOpts) EnvironmentIs(env string) repoopt.Option {
	return &repoopt.Filter[string]{
		Column: "environment",
		Value:  &env,
	}
}

func (_ dccrOpts) ReleaseNumberIs(number uint64) repoopt.Option {
	return &repoopt.Filter[uint64]{
		Column: "release_number",
		Value:  &number,
	}
}

func (_ dccrOpts) PhaseIn(phases ...ccent.DefaultConfigurationChangeReleasePhase) repoopt.Option {
	return &repoopt.Filter[ccent.DefaultConfigurationChangeReleasePhase]{
		Column: "phase",
		Values: phases,
	}
}

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

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

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

type dcctOpts struct{}

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

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

func (_ dcctOpts) OwnByChange(changeID uint64) repoopt.Option {
	return &repoopt.Filter[uint64]{
		Column: "parent_id",
		Value:  &changeID,
	}
}

func (_ dcctOpts) OwnByChanges(changeID []uint64) repoopt.Option {
	return &repoopt.Filter[uint64]{
		Column: "parent_id",
		Values: changeID,
	}
}

func (_ dcctOpts) TypeIs(typ ccent.DefaultConfigurationChangeItemType) repoopt.Option {
	return &repoopt.Filter[ccent.DefaultConfigurationChangeItemType]{
		Column: "item_type",
		Value:  &typ,
	}
}

func (_ dcctOpts) IDIn(ids []uint64) repoopt.Option {
	return &repoopt.Filter[uint64]{
		Column: "id",
		Values: ids,
	}
}

func (_ dcctOpts) DataIs(path string, value string) repoopt.Option {
	jsonColFormat := "JSON_EXTRACT(data, '$%s')"
	return &repoopt.Filter[string]{
		Column: fmt.Sprintf(jsonColFormat, path),
		Value:  &value,
	}
}

func (_ dcctOpts) TemplateDefaultCaseBindingHasConfigType(configTypes []string) repoopt.Option {
	jsonColFormat := "JSON_EXTRACT(data, '$.template_default_case_binding.origin.%s')"

	return &repoopt.Filter[string]{
		Column: fmt.Sprintf(jsonColFormat, "default_case.config_type"),
		Values: configTypes,
	}
}

func (_ dcctOpts) TemplateDefaultCaseBindingIs(clusterType ccent.ClusterType, defCase ccent.TemplateDefaultCase) repoopt.Option {
	jsonColFormat := "JSON_EXTRACT(data, '$.template_default_case_binding.origin.%s')"

	return repoopt.Aggregate(
		&repoopt.Filter[ccent.ClusterType]{
			Column: fmt.Sprintf(jsonColFormat, "cluster_type"),
			Value:  &clusterType,
		},
		&repoopt.Filter[string]{
			Column: fmt.Sprintf(jsonColFormat, "default_case.cloud_provider"),
			Value:  &defCase.CloudProvider,
		},
		&repoopt.Filter[string]{
			Column: fmt.Sprintf(jsonColFormat, "default_case.instance_type"),
			Value:  &defCase.InstanceType,
		},
		&repoopt.Filter[string]{
			Column: fmt.Sprintf(jsonColFormat, "default_case.version"),
			Value:  &defCase.Version,
		},
		&repoopt.Filter[string]{
			Column: fmt.Sprintf(jsonColFormat, "default_case.config_type"),
			Value:  &defCase.ConfigType,
		},
		&repoopt.Filter[string]{
			Column: fmt.Sprintf(jsonColFormat, "default_case.tenant_id"),
			Value:  &defCase.TenantID,
		},
	)
}

type dcctrOpts struct{}

func (_ dcctrOpts) OwnByChange(changeID uint64) repoopt.Option {
	return &repoopt.Filter[uint64]{
		Column: "parent_id",
		Value:  &changeID,
	}
}

func (_ dcctrOpts) EnvironmentIs(env string) repoopt.Option {
	return &repoopt.Filter[string]{
		Column: "environment",
		Value:  &env,
	}
}

func (_ dcctrOpts) ReleaseNumberIs(releaseNumber uint64) repoopt.Option {
	return &repoopt.Filter[uint64]{
		Column: "release_number",
		Value:  &releaseNumber,
	}
}

func (_ dcctrOpts) ReleaseNumberLessOrEqual(releaseNumber uint64) repoopt.Option {
	return &repoopt.Filter[uint64]{
		Column:      "release_number",
		LessOrEqual: &releaseNumber,
	}
}

func (_ dcctrOpts) ItemIDIn(ids []uint64) repoopt.Option {
	return &repoopt.Filter[uint64]{
		Column: "item_id",
		Values: ids,
	}
}
