package cloudconfig

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/config-center/gitopsfile"
	baseapi "github.com/tidbcloud/devops/api"
	auditent "github.com/tidbcloud/devops/entity/audit"
	ccent "github.com/tidbcloud/devops/entity/cloudconfig"
	"github.com/tidbcloud/devops/entity/event"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	"github.com/tidbcloud/devops/pkg/infra/errs"
	"github.com/tidbcloud/devops/pkg/infra/log"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"
	adsvc "github.com/tidbcloud/devops/pkg/service/audit"
	"github.com/tidbcloud/devops/pkg/service/cloudconfig/api"
	ccact "github.com/tidbcloud/devops/pkg/service/cloudconfig/change/defaultconfig"
	"github.com/tidbcloud/devops/pkg/service/cloudconfig/repo"
	tidb_mgmt_servicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
)

func (s *svc) ListDefaultConfigurationChanges(ctx context.Context, param api.ListDefaultConfigurationChangesParam) ([]*ccent.DefaultConfigurationChange, int64, error) {
	opts := []repoopt.Option{}
	optRepo := repo.DefaultConfigurationChangeOpts

	if param.ContainItemType != nil {
		opts = append(opts, optRepo.ContainItemType(*param.ContainItemType))
	}

	total, err := s.repo.CountDefaultConfigurationChange(ctx, opts...)
	if err != nil {
		return nil, 0, err
	}

	if param.Page > 0 {
		opts = append(opts, optRepo.WithPager(param.Page, param.PerPage))
	}

	changes, err := s.repo.ListDefaultConfigurationChanges(ctx, opts...)
	if err != nil {
		return nil, 0, err
	}

	return changes, total, nil
}

func (s *svc) GetDefaultConfigurationChange(ctx context.Context, cid uint64) (*ccent.DefaultConfigurationChange, error) {
	return s.repo.GetDefaultConfigurationChange(ctx, cid)
}

func (s *svc) CreateDefaultConfigurationChange(ctx context.Context, param api.CreateDefaultConfigurationChangeParam) (*uint64, error) {
	actor := ccact.NewDefaultConfigurationChangeActor(s.defConfigComps(), nil)

	err := s.dbp.Transaction(ctx, func(ctx context.Context) error {
		err := actor.Create(ctx, param)
		if err != nil {
			return err
		}

		err = s.buildAndRecordDefaultConfigurationChangeEvent(ctx, actor, event.CloudConfigDefaultConfigChangeEventTypeCreate)
		if err != nil {
			return err
		}

		return nil
	})

	var id *uint64
	if actor.GetContext().Change != nil {
		id = &actor.GetContext().Change.ID
	}

	return id, err
}

func (s *svc) BatchUpdateDefaultConfigurationChangeItems(ctx context.Context, cid uint64, param api.BatchUpdateDefaultConfigurationChangeItemsParam) error {
	chg, err := s.repo.GetDefaultConfigurationChange(ctx, cid)
	if err != nil {
		return err
	}

	actor := ccact.NewDefaultConfigurationChangeActor(s.defConfigComps(), chg)

	return s.dbp.Transaction(ctx, func(ctx context.Context) error {
		err := actor.Update(ctx, param)
		if err != nil {
			return err
		}

		err = s.buildAndRecordDefaultConfigurationChangeEvent(ctx, actor, event.CloudConfigDefaultConfigChangeEventTypeUpdate)
		if err != nil {
			return err
		}

		return nil
	})
}

func (s *svc) CreateDefaultConfigurationChangeRelease(ctx context.Context, cid uint64, env string, description string) error {
	chg, err := s.repo.GetDefaultConfigurationChange(ctx, cid)
	if err != nil {
		return err
	}

	actor := ccact.NewDefaultConfigurationChangeActor(s.defConfigComps(), chg)

	return s.dbp.Transaction(ctx, func(ctx context.Context) error {
		err := actor.ReleaseStart(ctx, env, description)
		if err != nil {
			return err
		}

		err = s.buildAndRecordDefaultConfigurationChangeEvent(ctx, actor, event.CloudConfigDefaultConfigChangeEventTypeReleaseStart)
		if err != nil {
			return err
		}

		return nil
	})
}

func (s *svc) CancelDefaultConfigurationChangeRelease(ctx context.Context, cid, releaseID uint64) error {
	chg, err := s.repo.GetDefaultConfigurationChange(ctx, cid)
	if err != nil {
		return err
	}

	actor := ccact.NewDefaultConfigurationChangeActor(s.defConfigComps(), chg)

	return s.dbp.Transaction(ctx, func(ctx context.Context) error {
		err := actor.ReleaseCancel(ctx, releaseID)
		if err != nil {
			return err
		}

		err = s.buildAndRecordDefaultConfigurationChangeEvent(ctx, actor, event.CloudConfigDefaultConfigChangeEventTypeReleaseCancel)
		if err != nil {
			return err
		}

		return nil
	})
}

func (s *svc) RollbackDefaultConfigurationChangeRelease(ctx context.Context, cid uint64, env string, number uint64, description string) error {
	chg, err := s.repo.GetDefaultConfigurationChange(ctx, cid)
	if err != nil {
		return err
	}

	actor := ccact.NewDefaultConfigurationChangeActor(s.defConfigComps(), chg)

	return s.dbp.Transaction(ctx, func(ctx context.Context) error {
		err := actor.Rollback(ctx, env, description, number)
		if err != nil {
			return err
		}

		err = s.buildAndRecordDefaultConfigurationChangeEvent(ctx, actor, event.CloudConfigDefaultConfigChangeEventTypeReleaseRollback)
		if err != nil {
			return err
		}

		return nil
	})
}

func (s *svc) CloseDefaultConfigurationChange(ctx context.Context, cid uint64, result ccent.DefaultConfigurationChangePhase, reason string) error {
	chg, err := s.repo.GetDefaultConfigurationChange(ctx, cid)
	if err != nil {
		return err
	}

	actor := ccact.NewDefaultConfigurationChangeActor(s.defConfigComps(), chg)

	return s.dbp.Transaction(ctx, func(ctx context.Context) error {
		err := actor.Close(ctx, result, reason)
		if err != nil {
			return err
		}

		err = s.buildAndRecordDefaultConfigurationChangeEvent(ctx, actor, event.CloudConfigDefaultConfigChangeEventTypeClose)
		if err != nil {
			return err
		}

		return nil
	})
}

func (s *svc) GetDefaultConfigurationChangeReleases(ctx context.Context, rid uint64) (*ccent.DefaultConfigurationChangeRelease, error) {
	return s.repo.GetDefaultConfigurationChangeRelease(ctx, rid)
}

func (s *svc) ListDefaultConfigurationChangeReleases(ctx context.Context, param api.ListDefaultConfigurationChangeReleasesParam) ([]*ccent.DefaultConfigurationChangeRelease, error) {
	opts := []repoopt.Option{}
	optRepo := repo.DefaultConfigurationChangeReleaseOpts

	if param.OwnByChange != nil {
		opts = append(opts, optRepo.OwnByChange(*param.OwnByChange))
	}
	if param.EnvironmentIs != nil {
		opts = append(opts, optRepo.EnvironmentIs(*param.EnvironmentIs))
	}
	if param.ReleaseNumberIs != nil {
		opts = append(opts, optRepo.ReleaseNumberIs(*param.ReleaseNumberIs))
	}
	opts = append(opts, optRepo.WithPager(0, 0)) // return all

	return s.repo.ListDefaultConfigurationChangeRelease(ctx, opts...)
}

func (s *svc) ListDefaultConfigurationChangeItems(ctx context.Context, param api.ListDefaultConfigurationChangeItemsParam) ([]*ccent.DefaultConfigurationChangeItem, int64, error) {
	opts := []repoopt.Option{}
	optRepo := repo.DefaultConfigurationChangeItemsOpts

	if param.OwnByChange != nil {
		opts = append(opts, optRepo.OwnByChange(*param.OwnByChange))
	}
	if param.TypeIs != nil {
		opts = append(opts, optRepo.TypeIs(*param.TypeIs))
	}
	if param.DataIs != nil {
		fields := strings.Split(*param.DataIs, "=")
		if len(fields) != 2 {
			return nil, 0, errs.APIErrorf(baseapi.ErrCodeInvalidArgument, "invalid data_is: %s", *param.DataIs)
		}

		opts = append(opts, optRepo.DataIs(fields[0], fields[1]))
	}
	if len(param.IDIn) != 0 {
		opts = append(opts, optRepo.IDIn(param.IDIn))
	}

	total, err := s.repo.CountDefaultConfigurationChangeItems(ctx, opts...)
	if err != nil {
		return nil, 0, err
	}

	if param.Page > 0 {
		opts = append(opts, optRepo.WithPager(param.Page, param.PerPage))
	}

	changes, err := s.repo.ListDefaultConfigurationChangeItems(ctx, opts...)
	if err != nil {
		return nil, 0, err
	}

	return changes, total, nil
}

func (s *svc) StatDefaultConfigurationChangeItems(ctx context.Context, param api.StatDefaultConfigurationChangeItemsParam) (*api.StatDefaultConfigurationChangeItemsResult, error) {
	opts := []repoopt.Option{}
	optRepo := repo.DefaultConfigurationChangeItemsOpts
	if param.OwnByChange != nil {
		opts = append(opts, repo.DefaultConfigurationChangeItemsOpts.OwnByChange(*param.OwnByChange))
	}

	result := &api.StatDefaultConfigurationChangeItemsResult{}

	if param.ConfigTypesOfTemplateDefaultCaseBinding {
		type Column struct {
			ConfigType string `gorm:"column:config_type"`
		}
		subResult := []Column{}

		opts := append(opts,
			&repoopt.Select{
				Select: []string{"DISTINCT REPLACE(JSON_EXTRACT(data, '$.template_default_case_binding.origin.default_case.config_type'), '\"', '') AS config_type"},
			},
			optRepo.TypeIs(ccent.DefaultConfigurationChangeItemTypeTemplateDefaultCaseBinding),
		)

		err := s.repo.StatDefaultConfigurationChangeItems(ctx, &subResult, opts...)
		if err != nil {
			return nil, fmt.Errorf("failed to stat config types of template default case binding: %s", err)
		}
		result.ConfigTypesOfTemplateDefaultCaseBinding = container.TransformSlice(subResult, func(e Column) string { return e.ConfigType })
	}

	return result, nil
}

func (s *svc) ListDefaultConfigurationChangeItemReleases(ctx context.Context, param api.ListDefaultConfigurationChangeItemReleasesParam) ([]*ccent.DefaultConfigurationChangeItemRelease, error) {
	opts := []repoopt.Option{}
	optRepo := repo.DefaultConfigurationChangeItemReleaseOpts

	if param.OwnByChange != nil {
		opts = append(opts, optRepo.OwnByChange(*param.OwnByChange))
	}
	if param.EnvironmentIs != nil {
		opts = append(opts, optRepo.EnvironmentIs(*param.EnvironmentIs))
	}
	if param.ReleaseNumberIs != nil {
		opts = append(opts, optRepo.ReleaseNumberIs(*param.ReleaseNumberIs))
	}
	if len(param.ItemIDIn) != 0 {
		opts = append(opts, optRepo.ItemIDIn(param.ItemIDIn))
	}

	if param.LatestPerItem {
		return s.repo.ListLastestDefaultConfigurationChangeItemReleasesPerItem(ctx, opts...)
	}
	return s.repo.ListDefaultConfigurationChangeItemReleases(ctx, opts...)
}

func (s *svc) WatchDefaultConfigurationChangeReleases(ctx context.Context) error {
	logger := log.FromContext(ctx)

	releases, err := s.repo.ListDefaultConfigurationChangeRelease(ctx,
		repo.DefaultConfigurationChangeReleaseOpts.PhaseIn(ccent.DefaultConfigurationChangeReleasePhaseApproving),
	)
	if err != nil {
		return err
	}

	if len(releases) != 0 {
		logger.Info("watched some default config change releases need to sync",
			"release_ids", container.TransformSlice(releases, func(i *ccent.DefaultConfigurationChangeRelease) uint64 { return i.ID }),
		)
	}

	for _, release := range releases {
		s.syncDefChgRelChan <- release.ID
	}

	return nil
}

func (s *svc) SyncDefaultConfigurationChangeRelease(ctx context.Context, rid uint64) error {
	release, err := s.repo.GetDefaultConfigurationChangeRelease(ctx, rid)
	if err != nil {
		return fmt.Errorf("get releases failed: %w", err)
	}

	chg, err := s.repo.GetDefaultConfigurationChange(ctx, release.ParentID)
	if err != nil {
		return fmt.Errorf("get change failed: %w", err)
	}

	actor := ccact.NewDefaultConfigurationChangeActor(s.defConfigComps(), chg)
	err = s.dbp.Transaction(ctx, func(ctx context.Context) error {
		err := actor.ReleaseIfApproved(ctx, rid)
		if err != nil {
			return err
		}

		err = s.buildAndRecordDefaultConfigurationChangeEvent(ctx, actor, event.CloudConfigDefaultConfigChangeEventTypeReleaseApply)
		if err != nil {
			return err
		}

		return nil
	})

	return err
}

func (s *svc) defConfigComps() ccact.Components {
	return ccact.Components{
		Conf:            s.conf,
		Repo:            s.repo,
		ConfigService:   s,
		ApprovalService: s.apprSvc,
		SubEnvironments: s.subEnvClients,
	}
}

func (s *svc) buildAndRecordDefaultConfigurationChangeEvent(ctx context.Context, actor ccact.DefaultConfigurationChangeActor, typ event.EventType) error {
	change := actor.GetContext().Change
	detail := actor.GetContext().EventDetail

	ev := &event.CloudConfigDefaultConfigChangeEvent{
		BaseEvent: event.BaseEvent{
			ID:            change.ID,
			OperatorEmail: consts.GetOperatorFromCtx(ctx, consts.InternalVirtualEmail),
			OperateAt:     pointer.Pointer(time.Now()),
			EventType:     typ,
		},
	}
	ev.Details = detail

	auditLog, err := adsvc.EventToAuditLog(ev.BaseEvent, auditent.AuditLogTypeCloudConfigDefaultConfigChange, ev.Details)
	if err != nil {
		return err
	}

	err = s.auditSvc.Create(ctx, auditLog)
	if err != nil {
		return fmt.Errorf("create audit log failed: %w", err)
	}

	return nil
}

func (s *svc) SyncVersionsFromGitops(ctx context.Context, minVersion, maxVersion, gitopsBranch string) (err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("panic: %v", r)
		}
	}()

	_, err = s.githubCli.Clone(ctx, "tidbcloud", "tidb-configuration", gitopsBranch)
	if err != nil {
		return fmt.Errorf("clone tidb-configuration failed: %w", err)
	}
	/*
		_, err = s.githubCli.Clone(ctx, "tidbcloud", "cluster-tier-config", tierBranch)
		if err != nil {
			return fmt.Errorf("clone cluster-tier-config failed: %w", err)
		}*/
	//return gitopsfile.ImportBaseFromGitops(s.cfgCenter.ConfigAPI, "/tmp/github/tidbcloud/tidb-configuration", "/tmp/github/tidbcloud/cluster-tier-config", minVersion, maxVersion, false)

	allInstances := map[string]map[string]bool{
		"pd": {
			"": true, // all instances shared the same pd config
		},
		"restore": {
			"": true, // all instances shared the same restore config
		},
		"ticdc": {
			"": true, // all instances shared the same ticdc config
		},
		"backup": {
			"": true, // all instances shared the same backup config
		},
		"tidb-lightning": {
			"": true, // all instances shared the same tidb-lightning config
		},
		"tidb":            {},
		"tikv":            {},
		"tiflash":         {},
		"tiflash_learner": {},
	}

	skus, err := s.coreSvc.Mgmt.SpuSku.ListSkus(ctx, &tidb_mgmt_servicev1.ListSkusRequest{})
	if err != nil {
		return fmt.Errorf("list skus failed: %w", err)
	}
	for _, sku := range skus.Skus {
		componentType := strings.ToLower(sku.ComponentType.String())
		cloudProvider := strings.ToLower(sku.CloudProvider.String())
		instanceType := sku.InstanceType
		switch componentType {
		case "tidb", "tikv":
			// %5s is necessary to align the cloud provider to 5 bytes
			allInstances[componentType][fmt.Sprintf("%5s.%s", cloudProvider, instanceType)] = true
		case "tiflash":
			allInstances["tiflash"][fmt.Sprintf("%5s.%s", cloudProvider, instanceType)] = true
			allInstances["tiflash_learner"][fmt.Sprintf("%5s.%s", cloudProvider, instanceType)] = true
		}
	}

	gitopsPath := "/tmp/github/tidbcloud/tidb-configuration"
	err = gitopsfile.ImportDefaultTemplatesFromGitops(s.cfgCenter.ConfigAPI, gitopsPath, minVersion, maxVersion, allInstances)
	if err != nil {
		return err
	}
	err = gitopsfile.ImportInitSql(s.cfgCenter.ConfigAPI, gitopsPath, minVersion, maxVersion)
	if err != nil {
		return err
	}
	return gitopsfile.ImportImagesFromGitops(s.cfgCenter.ConfigAPI, gitopsPath, minVersion, maxVersion, false)
}
