package release

import (
	"context"
	"time"

	userv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/core-server/user/v1"
	"github.com/tidbcloud/devops/entity/event"
	"github.com/tidbcloud/devops/pkg/infra/clients/eecli"
	"github.com/tidbcloud/devops/pkg/infra/clients/fadcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/jiracli"
	"github.com/tidbcloud/devops/pkg/infra/clients/larkcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/onescli"
	"github.com/tidbcloud/devops/pkg/infra/config"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	"github.com/tidbcloud/devops/pkg/infra/eventbus"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	"github.com/tidbcloud/devops/pkg/service"
	"github.com/tidbcloud/devops/pkg/service/application"
	apprsvcapi "github.com/tidbcloud/devops/pkg/service/approval/api"
	"github.com/tidbcloud/devops/pkg/service/github"
	"github.com/tidbcloud/devops/pkg/service/release/action"
	relsvcapi "github.com/tidbcloud/devops/pkg/service/release/api"
	"github.com/tidbcloud/devops/pkg/service/release/repo"
	"github.com/tidbcloud/devops/pkg/service/release/source"

	"github.com/go-logr/logr"
	"github.com/tidbcloud/pkgv2/db"
	"go.uber.org/dig"
)

const (
	groupKey = "devops_change_service"
)

var _ relsvcapi.ChangeService = &svc{}

type ServiceParam struct {
	dig.In

	Conf       *config.RootConfig
	Logger     logr.Logger
	EventBus   *eventbus.EventBus
	DBProvider db.TransProvider
	LarkClient *larkcli.LarkClient
	FADClient  fadcli.Client
	JiraClient jiracli.JiraClient
	OnesClient onescli.OnesClient
	EEClient   eecli.Client

	UserAPI        userv1.UserServiceClient
	Approval       apprsvcapi.ApprovalServiceAPI
	ApplicationAPI application.ApplicationService
	GithubAPI      github.GithubServiceAPI
}

type ServiceResult struct {
	dig.Out

	API     relsvcapi.ServiceAPI
	Service service.Service `group:"service"`
}

type svc struct {
	*service.JobService

	conf config.ReleaseSerivceConfig

	logger   logr.Logger
	dbp      db.Transactioner
	eventbus *eventbus.EventBus
	larkCli  *larkcli.LarkClient
	fadCli   fadcli.Client

	userSvc userv1.UserServiceClient
	applSvc application.ApplicationService
	apprSvc apprsvcapi.ApprovalServiceAPI
	ghSvc   github.GithubServiceAPI

	repo         repo.Repo
	source       source.Source
	eeCli        eecli.Client
	syncChangeCh chan uint64
	syncTicketCh chan []string
}

func NewService(p ServiceParam) (ServiceResult, error) {
	s := &svc{
		JobService: service.NewJobService("ReleaseService"),

		conf:     p.Conf.Services.Release,
		logger:   p.Logger.WithName("ChangeService"),
		dbp:      p.DBProvider,
		eventbus: p.EventBus,
		larkCli:  p.LarkClient,
		fadCli:   p.FADClient,
		userSvc:  p.UserAPI,
		applSvc:  p.ApplicationAPI,
		apprSvc:  p.Approval,
		ghSvc:    p.GithubAPI,
		source:   nil,
		eeCli:    p.EEClient,

		repo:         repo.NewRepo(p.DBProvider),
		syncChangeCh: make(chan uint64, 10),
		syncTicketCh: make(chan []string, 10),
	}

	if consts.GetPartition() == consts.PartitionCN {
		s.source = source.NewSourceOnes(p.OnesClient, p.Conf.Services.Release.Project)
	} else {
		s.source = source.NewSourceJira(p.JiraClient, p.Conf.Services.Release.Project)
	}

	s.AddJob(
		service.NewConstantCronJob("WatchChanges", *s.conf.WatchChangeInterval, s.WatchChanges),
		service.NewEventJob("SyncChange", s.syncChangeCh, s.SyncChange),
		service.MustNewCronJob("SyncReleases", "0 1,13 * * *", s.SyncRelease,
			service.WithInitDelay(*s.conf.SyncReleaseFromJiraInitDelay)),
		service.NewConstantCronJob("WatchTickets", *s.conf.WatchTicketsFromJiraInterval, s.WatchTickets),
		service.NewEventJob("SyncTicket", s.syncTicketCh, s.SyncTickets),
	)

	s.eventbus.ApprovalEvent.Subscribe(groupKey, s.HandleApprovalEvent)

	return ServiceResult{API: s, Service: s}, nil
}

func buildEvent(ctx context.Context, objs *action.ActionContext, typ event.EventType) *event.ChangeEvent {
	ev := &event.ChangeEvent{
		BaseEvent: event.BaseEvent{
			ID:            objs.Change.ID,
			OperatorEmail: consts.GetOperatorFromCtx(ctx, "unknown"),
			OperateAt:     pointer.Pointer(time.Now()),
			EventType:     typ,
		},
	}

	if objs.EventDetail != nil {
		ev.Details = *objs.EventDetail
	}

	return ev
}
