package operation

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/billingcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/byocimagesynccli"
	"github.com/tidbcloud/devops/pkg/infra/clients/cliniccli"
	"github.com/tidbcloud/devops/pkg/infra/clients/configcentercli"
	"github.com/tidbcloud/devops/pkg/infra/clients/coresvccli"
	"github.com/tidbcloud/devops/pkg/infra/clients/dataflowcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/dedicatedcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/fadcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/fidcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/githubcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/larkcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/llm_cli"
	"github.com/tidbcloud/devops/pkg/infra/clients/mgmthttpcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/oncallcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/serverlesscli"
	"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"
	apprsvcapi "github.com/tidbcloud/devops/pkg/service/approval/api"
	auditsvc "github.com/tidbcloud/devops/pkg/service/audit"
	cloudconfigapi "github.com/tidbcloud/devops/pkg/service/cloudconfig/api"
	cmxapi "github.com/tidbcloud/devops/pkg/service/clustermaintenance/api"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
	actregistry "github.com/tidbcloud/devops/pkg/service/operation/actions/registry"
	"github.com/tidbcloud/devops/pkg/service/operation/instance"
	"github.com/tidbcloud/devops/pkg/service/operation/operation"
	"github.com/tidbcloud/devops/pkg/service/operation/repo"
	"github.com/tidbcloud/devops/pkg/service/project"
	"github.com/tidbcloud/devops/pkg/service/tenant"

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

const (
	groupKey = "devops_operation_service"
)

type ServiceParam struct {
	dig.In

	Conf            *config.RootConfig
	Logger          logr.Logger
	DBProvider      db.TransProvider
	UserService     userv1.UserServiceClient
	TenantService   tenant.TenantServiceAPI
	ProjectService  project.ProjectServiceAPI
	CloudConfigAPI  cloudconfigapi.CloudConfigServiceAPI
	MaintenanceSvc  cmxapi.ServiceAPI
	ApprovalService apprsvcapi.ApprovalServiceAPI
	LarkCli         *larkcli.LarkClient
	CoreSvcCli      *coresvccli.CoreSvcCli
	MgmtHTTP        mgmthttpcli.MgmtHTTPClient
	EventBus        *eventbus.EventBus
	BillingCli      billingcli.Client
	GithubActionCli githubcli.Client
	DataflowCli     *dataflowcli.DataflowCli
	FADCli          fadcli.Client
	FIDCli          fidcli.Client
	BYOCSyncCli     byocimagesynccli.Client
	ClinicCli       cliniccli.Client `optional:"true"`
	LLMCli          *llm_cli.LLMCli  `optional:"true"`
	OnCallCli       oncallcli.Client `optional:"true"`
	AuditService    auditsvc.AuditServiceAPI
	InfraAPICli     dedicatedcli.InfraAPIClient
	ServerlessCli   *serverlesscli.Client
	CfgCenterCli    *configcentercli.Client
}

type ServiceResult struct {
	dig.Out

	API     OperationServiceAPI
	Service service.Service `group:"service"`
}

type svc struct {
	*service.JobService

	conf       config.OperationServiceConfig
	userSvc    userv1.UserServiceClient
	apprSvc    apprsvcapi.ApprovalServiceAPI
	maintSvc   cmxapi.ServiceAPI
	tenantSvc  tenant.TenantServiceAPI
	dbp        db.TransProvider
	eventbus   *eventbus.EventBus
	coresvcCli *coresvccli.CoreSvcCli

	opRepo      repo.OperationRepo
	opRegistry  *operation.Registry
	actRegistry actregistry.Registry
	syncInsCh   chan uint64 // a channel to sync a instance
}

func NewService(p ServiceParam) (ServiceResult, error) {
	var err error

	s := &svc{
		JobService: service.NewJobService("OperationService"),
		conf:       p.Conf.Services.Operation,
		userSvc:    p.UserService,
		apprSvc:    p.ApprovalService,
		tenantSvc:  p.TenantService,
		dbp:        p.DBProvider,
		eventbus:   p.EventBus,
		coresvcCli: p.CoreSvcCli,
		syncInsCh:  make(chan uint64, 100),
		maintSvc:   p.MaintenanceSvc,
	}

	s.opRepo = repo.NewOperationRepo(s.dbp)
	s.opRegistry, err = operation.InitRegistry(s.conf.OperationRegistryFilepath, p.ApprovalService)
	if err != nil {
		return ServiceResult{}, err
	}
	s.actRegistry, err = actregistry.NewDefaultRegistry(&actions.Components{
		Conf:           p.Conf.Services.Operation,
		Logger:         p.Logger,
		LarkCli:        p.LarkCli,
		UserAPI:        p.UserService,
		TenantAPI:      p.TenantService,
		ProjectAPI:     p.ProjectService,
		CloudConfigAPI: p.CloudConfigAPI,
		CoreSvc:        p.CoreSvcCli,
		ClinicCli:      p.ClinicCli,
		GeminiCli: func() *genai.Client {
			if p.LLMCli != nil {
				return p.LLMCli.Gemini
			}
			return nil
		}(),
		EventBus:        p.EventBus,
		MgmtHTTP:        p.MgmtHTTP,
		OpRepo:          s.opRepo,
		BillingCli:      p.BillingCli,
		GithubActionCli: p.GithubActionCli,
		DataflowCli:     p.DataflowCli,
		FadCli:          p.FADCli,
		FIDCli:          p.FIDCli,
		BYOCSyncCli:     p.BYOCSyncCli,
		KernelImageConf: p.Conf.Services.KernelImage,
		AuditService:    p.AuditService,
		InfraAPICli:     p.InfraAPICli,
		ServerlessCli:   p.ServerlessCli,
		CfgCenterCli:    p.CfgCenterCli,
		OnCallCli:       p.OnCallCli,
		DBProvider:      p.DBProvider,
	})
	if err != nil {
		return ServiceResult{}, err
	}

	s.eventbus.ApprovalEvent.Subscribe(groupKey, s.HandleApprovalEvent)
	s.AddJob(
		service.NewConstantCronJob("WatchInstances", *s.conf.SyncInstanceInterval, s.WatchInstances),
		service.NewEventJob("SyncInstance", s.syncInsCh, s.SyncInstance).
			With(service.WithConcurrency(10)).
			Use(service.DedupeMiddleware(func(k uint64) uint64 { return k })),
	)

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

func buildEvent(ctx context.Context, objs *instance.Context, typ event.EventType) *event.OperationEvent {
	ev := &event.OperationEvent{
		BaseEvent: event.BaseEvent{
			ID:            objs.Instance.ID,
			OperatorEmail: consts.GetOperatorFromCtx(ctx, consts.InternalVirtualEmail),
			OperateAt:     pointer.Pointer(time.Now()),
			EventType:     typ,
		},
		Operation: objs.Instance.Operation,
	}

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

	return ev
}
