package cloudconfig

import (
	"context"
	"fmt"
	"slices"
	"time"

	ccent "github.com/tidbcloud/devops/entity/cloudconfig"
	"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/githubcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/mgmthttpcli"
	"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/container"
	"github.com/tidbcloud/devops/pkg/service"
	apprapi "github.com/tidbcloud/devops/pkg/service/approval/api"
	adsvc "github.com/tidbcloud/devops/pkg/service/audit"
	"github.com/tidbcloud/devops/pkg/service/cloudconfig/api"
	"github.com/tidbcloud/devops/pkg/service/cloudconfig/cvt"
	"github.com/tidbcloud/devops/pkg/service/cloudconfig/repo"
	cmxapi "github.com/tidbcloud/devops/pkg/service/clustermaintenance/api"
	"github.com/tidbcloud/devops/pkg/service/tenant"

	"github.com/go-resty/resty/v2"
	ccproto "github.com/tidbcloud/config-center/proto/config_center"
	"github.com/tidbcloud/pkgv2/db"
	"go.uber.org/dig"
)

const (
	groupKey = "devops_cloudconfig_service"
)

type ServiceParam struct {
	dig.In

	Conf            *config.RootConfig
	DBProvider      db.TransProvider
	EventBus        *eventbus.EventBus
	ConfigCenterCli *configcentercli.Client
	CoreSvc         *coresvccli.CoreSvcCli
	Dataflow        *dataflowcli.DataflowCli
	ServerlessCli   *serverlesscli.Client
	ApprovalService apprapi.ApprovalServiceAPI
	MaintenanceSvc  cmxapi.ServiceAPI
	TenantService   tenant.TenantServiceAPI
	AuditService    adsvc.AuditServiceAPI
	GithubCli       *githubcli.GithubClient
	MgmtHTTP        mgmthttpcli.MgmtHTTPClient
}

type ServiceResult struct {
	dig.Out

	API     api.CloudConfigServiceAPI
	Service service.Service `group:"service"`
}

type svc struct {
	*service.JobService

	conf config.CloudConfigServiceConfig

	syncChgChan       chan uint64
	syncDefChgRelChan chan uint64
	repo              repo.CloudConfigRepo
	dbp               db.TransProvider
	cfgCenter         *configcentercli.Client
	coreSvc           *coresvccli.CoreSvcCli
	dataflow          *dataflowcli.DataflowCli
	serverlessCli     *serverlesscli.Client
	apprSvc           apprapi.ApprovalServiceAPI
	maintenanceSvc    cmxapi.ServiceAPI
	tenantSvc         tenant.TenantServiceAPI
	auditSvc          adsvc.AuditServiceAPI
	eventbus          *eventbus.EventBus
	subEnvClients     map[string]*resty.Client
	githubCli         *githubcli.GithubClient
	mgmtHTTP          mgmthttpcli.MgmtHTTPClient
}

func NewService(p ServiceParam) (ServiceResult, error) {
	base := service.NewJobService("CloudConfigService", service.WithDummy(p.Conf.Services.CloudConfig.Dummy))
	if p.Conf.Services.CloudConfig.Dummy {
		return ServiceResult{
			API:     &api.DummyCloudConfigServiceAPI{},
			Service: base,
		}, nil
	}

	s := &svc{
		JobService: base,
		conf:       p.Conf.Services.CloudConfig,

		syncChgChan:       make(chan uint64, 10),
		syncDefChgRelChan: make(chan uint64, 10),
		dbp:               p.DBProvider,
		cfgCenter:         p.ConfigCenterCli,
		coreSvc:           p.CoreSvc,
		dataflow:          p.Dataflow,
		serverlessCli:     p.ServerlessCli,
		apprSvc:           p.ApprovalService,
		maintenanceSvc:    p.MaintenanceSvc,
		tenantSvc:         p.TenantService,
		auditSvc:          p.AuditService,
		eventbus:          p.EventBus,
		subEnvClients:     map[string]*resty.Client{},
		githubCli:         p.GithubCli,
		mgmtHTTP:          p.MgmtHTTP,
	}

	for _, subEnv := range s.conf.SubEnvironments {
		if _, exist := s.subEnvClients[subEnv]; exist {
			return ServiceResult{}, fmt.Errorf("sub environment %s is duplicate", subEnv)
		}

		apiKey := s.conf.SubEnvironmentAPIKeys[subEnv]
		if apiKey == "" {
			return ServiceResult{}, fmt.Errorf("sub environment %s has no api key", subEnv)
		}
		addr := consts.ServerAddrOfEnv(consts.Environment(subEnv))
		if addr == "" {
			return ServiceResult{}, fmt.Errorf("sub environment %s has no address", subEnv)
		}

		s.subEnvClients[subEnv] = resty.New().
			SetScheme("https").
			SetBaseURL(addr).
			SetTimeout(10*time.Second).
			SetHeader(consts.HTTPHeaderAPIKey, apiKey). // set api key
			OnAfterResponse(func(c *resty.Client, r *resty.Response) error {
				if !r.IsSuccess() {
					return fmt.Errorf("status: %s, body: %s", r.Status(), string(r.Body()))
				}
				return nil
			})
	}

	s.repo = repo.NewCloudConfigRepo(s.dbp)

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

	s.AddJob(
		service.NewConstantCronJob("WatchConfigurationChange", *s.conf.SyncClusterConfigChangeInterval, s.WatchConfigurationChanges),
		service.NewEventJob("SyncConfigurationChange", s.syncChgChan, s.SyncConfigurationChange),

		service.NewConstantCronJob("WatchDefaultConfigurationChangeReleases", *s.conf.SyncDefaultConfigChangeReleaseInterval, s.WatchDefaultConfigurationChangeReleases),
		service.NewEventJob("SyncDefaultConfigurationChangeRelease", s.syncDefChgRelChan, s.SyncDefaultConfigurationChangeRelease),
	)

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

func (s *svc) UpdateDefaultClusterVersion(ctx context.Context, typ ccent.ClusterType, version string, param api.UpdateDefaultVersionParam) error {
	req := &ccproto.UpdateDefaultVersionRequest{
		ClusterType: cvt.To.ConfigCenter.ClusterType(typ),
		Version:     version,
	}

	switch {
	case param.ForGlobal:
	case param.ForTenant != nil:
		req.TenantId = param.ForTenant
	default:
		return fmt.Errorf("invalid param with no specified target")
	}

	_, err := s.cfgCenter.ConfigAPI.UpdateDefaultVersion(ctx, req)
	return err
}

func (s *svc) ListAvailableClusterVersions(ctx context.Context, typ ccent.ClusterType) ([]*ccent.AvailableClusterVersion, error) {
	req := ccproto.ListAvailableVersionsRequest{
		ClusterType: cvt.To.ConfigCenter.ClusterType(typ),
	}

	resp, err := s.cfgCenter.ConfigAPI.ListAvailableVersions(ctx, &req)
	if err != nil {
		return nil, err
	}

	slices.Sort(resp.Versions)
	slices.Reverse(resp.Versions)

	results := container.TransformSlice(resp.Versions, func(v string) *ccent.AvailableClusterVersion {
		return &ccent.AvailableClusterVersion{
			ClusterType: typ,
			Version:     v,
		}
	})

	defaultVer, err := s.cfgCenter.ConfigAPI.GetDefaultVersion(ctx, &ccproto.GetDefaultVersionRequest{
		ClusterType: cvt.To.ConfigCenter.ClusterType(typ),
	})
	if err != nil {
		return nil, fmt.Errorf("get default version failed: %w", err)
	}

	for _, result := range results {
		if result.Version == defaultVer.Version {
			result.IsDefault = true
			break
		}
	}

	return results, nil
}

func (s *svc) ListAvailableComponentVersions(ctx context.Context, typ ccent.ClusterType, comp string) ([]string, error) {
	req := ccproto.ListAvailableVersionsRequest{
		ClusterType: cvt.To.ConfigCenter.ClusterType(typ),
		Component:   comp,
	}

	resp, err := s.cfgCenter.ConfigAPI.ListAvailableVersions(ctx, &req)
	if err != nil {
		return nil, err
	}

	return resp.Versions, nil
}

func (s *svc) ListAvailableInstanceTypes(ctx context.Context, typ ccent.ClusterType, version, configType string) ([]*ccent.CloudInstanceType, error) {
	req := ccproto.ListAvailableInstancesRequest{
		ClusterType: cvt.To.ConfigCenter.ClusterType(typ),
		Version:     &version,
		ConfigType:  &configType,
	}

	resp, err := s.cfgCenter.ConfigAPI.ListAvailableInstances(ctx, &req)
	if err != nil {
		return nil, err
	}

	results := container.TransformSlice(resp.Instances, cvt.From.ConfigCenter.CloudInstanceType)
	return results, nil
}

func (s *svc) UpdateDefaultComponents(ctx context.Context, comps []*ccent.DefaultComponentConfig) error {
	for _, comp := range comps {
		req := &ccproto.UpdateComponentImageRequest{
			ClusterType:      cvt.To.ConfigCenter.ClusterType(comp.DefaultCase.ClusterType),
			ComponentName:    comp.DefaultCase.Component,
			ComponentVersion: comp.DefaultCase.Version,
			Tag:              comp.ImageTag,
			BaseImage:        comp.BaseImage,
		}

		_, err := s.cfgCenter.ConfigAPI.UpdateComponentImage(ctx, req)
		if err != nil {
			return fmt.Errorf("update via config center failed: %w", err)
		}
	}

	return nil
}

func (s *svc) GetDefaultComponent(ctx context.Context, defCase *ccent.ComponentDefaultCase) (*ccent.DefaultComponentConfig, error) {
	req := &ccproto.GetComponentImageRequest{
		ClusterType:      cvt.To.ConfigCenter.ClusterType(defCase.ClusterType),
		ComponentName:    defCase.Component,
		ComponentVersion: defCase.Version,
	}

	resp, err := s.cfgCenter.ConfigAPI.GetComponentImage(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("get via config center failed: %w", err)
	}

	return &ccent.DefaultComponentConfig{
		DefaultCase: *defCase,
		BaseImage:   resp.BaseImage,
		ImageTag:    resp.Tag,
	}, nil

}
