package repo

import (
	"encoding/json"
	"time"

	jiraent "github.com/tidbcloud/devops/entity/jira"
	jiramod "github.com/tidbcloud/devops/models/jira"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"

	"gorm.io/gorm"
)

var (
	IssueOption                    iOpts
	IssueSnapshotOption            isOpts
	IssueCustomerInfoOption        iciOpts
	IssueTriageOption              itOpts
	IssueTriageTagDefinitionOption ittdOpts
)

type iOpts struct{}

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) KeyIn(keys []string) repoopt.Option {
	return &repoopt.Filter[string]{
		Column: "`key`",
		Values: keys,
	}
}

func (_ iOpts) HaveTriage(have bool) repoopt.Option {
	return &repoopt.Filter[bool]{
		Column: "have_triage",
		Value:  pointer.Pointer(have),
	}
}

func (_ iOpts) HaveTimelineSummary(have bool) repoopt.Option {
	return &repoopt.Filter[bool]{
		Column: "have_timeline_summary",
		Value:  pointer.Pointer(have),
	}
}

func (_ iOpts) FilterSnapshot(
	subFilters ...repoopt.Option,
) repoopt.Option {
	if len(subFilters) == 0 {
		return nil
	}

	subQ := &repoopt.Select{
		Module: jiramod.IssueSnapshot{},
		Select: []string{"`key`"},

		Filter: repoopt.Aggregate(subFilters...),
	}

	return &repoopt.FilterWithSubQuery{
		Column:   "`key`",
		SubQuery: subQ,
		Values:   true,
	}
}

func (_ iOpts) FilterCustomerInfo(
	subFilters ...repoopt.Option,
) repoopt.Option {
	if len(subFilters) == 0 {
		return nil
	}

	subQ := &repoopt.Select{
		Module: jiramod.IssueCustomerInfo{},
		Select: []string{"issue_key"},

		Filter: repoopt.Aggregate(subFilters...),
	}

	return &repoopt.FilterWithSubQuery{
		Column:   "`key`",
		SubQuery: subQ,
		Values:   true,
	}
}

type isOpts struct{}

func (_ isOpts) StatusIn(status []string) repoopt.Option {
	return &repoopt.Filter[string]{
		Column: "status",
		Values: status,
	}
}

func (_ isOpts) PriorityIn(p []string) repoopt.Option {
	return &repoopt.Filter[string]{
		Column: "priority",
		Values: p,
	}
}

func (_ isOpts) CreatedAfter(t time.Time) repoopt.Option {
	return &repoopt.Filter[time.Time]{
		Column:       "created_at",
		LargeOrEqual: pointer.Pointer(t),
	}
}

func (_ isOpts) CreatedBefore(t time.Time) repoopt.Option {
	return &repoopt.Filter[time.Time]{
		Column:      "created_at",
		LessOrEqual: pointer.Pointer(t),
	}
}

func (_ isOpts) ProjectKeyIn(keys []string) repoopt.Option {
	return &repoopt.Filter[string]{
		Column: "project_key",
		Values: keys,
	}
}

func (_ isOpts) ComponentIn(components []string) repoopt.Option {
	if len(components) == 0 {
		return nil
	}

	data, err := json.Marshal(components)
	if err != nil {
		panic(err)
	}

	return &repoopt.Raw{
		RawApply: func(db *gorm.DB) *gorm.DB {
			return db.Where("JSON_OVERLAPS(components, ?)", string(data))
		},
	}
}

type iciOpts struct{}

func (_ iciOpts) EnvironmentIs(env jiraent.IssueCustomerEnvironment) repoopt.Option {
	subQ := &repoopt.Select{
		Module: jiramod.IssueCustomerInfo{},
		Select: []string{"issue_key"},

		Filter: &repoopt.Filter[string]{
			Column: "environment",
			Value:  pointer.Pointer(string(env)),
		},
	}

	return &repoopt.FilterWithSubQuery{
		Column:   "`key`",
		SubQuery: subQ,
		Values:   true,
	}
}

func (_ iciOpts) ClusterIDIn(clusterIDs []string) repoopt.Option {
	return &repoopt.Filter[string]{
		Column: "cluster_id",
		Values: clusterIDs,
	}
}

func (_ iciOpts) TenantIDIn(tenantIDs []string) repoopt.Option {
	return &repoopt.Filter[string]{
		Column: "tenant_id",
		Values: tenantIDs,
	}
}

type itOpts struct{}

func (_ itOpts) KeyIn(keys []string) repoopt.Option {
	return &repoopt.Filter[string]{
		Column: "`key`",
		Values: keys,
	}
}

type ittdOpts struct{}

func (_ ittdOpts) TypesIn(types []jiraent.IssueTriageTagType) repoopt.Option {
	return &repoopt.Filter[jiraent.IssueTriageTagType]{
		Column: "type",
		Values: types,
	}
}

func (_ ittdOpts) NamesIn(names []string) repoopt.Option {
	return &repoopt.Filter[string]{
		Column: "name",
		Values: names,
	}
}

func (_ ittdOpts) LabelsContain(labels map[string]string) repoopt.Option {
	if len(labels) == 0 {
		return nil
	}

	data, err := json.Marshal(labels)
	if err != nil {
		panic(err)
	}

	return &repoopt.Raw{
		RawApply: func(db *gorm.DB) *gorm.DB {
			return db.Where("JSON_CONTAINS(labels, ?)", string(data))
		},
	}
}
