package clusteraction

import (
	"context"
	"fmt"
	"strconv"

	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
	"github.com/tidbcloud/devops/pkg/service/operation/repo"
	v1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"google.golang.org/protobuf/encoding/protojson"
)

var (
	ActionMigrateClusterPrivateLink opent.ActionMetadata = opent.ActionMetadata{
		Name: "MigrateClusterPrivateLink",
		Type: opent.ActionTypeAuto,
	}
)

type MigrateClusterPrivateLink struct {
	actions.BaseAction
	comps *actions.Components
}

func NewMigrateClusterPrivateLink(comps *actions.Components) actions.AutoAction {
	return &MigrateClusterPrivateLink{comps: comps}
}

func (a *MigrateClusterPrivateLink) Metadata() opent.ActionMetadata {
	return ActionMigrateClusterPrivateLink
}

func (a *MigrateClusterPrivateLink) PreCheck(ctx context.Context, instance *opent.Instance) error {
	req, err := a.getParam(ctx, instance)
	if err != nil {
		return err
	}
	req.ValidateOnly = true

	// check any existing migration
	existingInstances, err := a.comps.OpRepo.ListInstances(ctx,
		repo.InstanceOpts.OperationIn([]string{opent.OperationTypeMigratePrivateLinkForCluster}),
		repo.InstanceOpts.PhaseNotIn(opent.InstancePhaseFinishedValues),
	)
	if err != nil {
		return fmt.Errorf("list existing privatelink migration failed: %w", err)
	}
	for _, existIns := range existingInstances {
		existReq, err := a.getParam(ctx, existIns)
		if err != nil {
			return fmt.Errorf("parse param for existing instance '%d' failed: %w", existIns.ID, err)
		}
		if existReq.Migration.SourceClusterId != req.Migration.SourceClusterId {
			continue
		}
		if existReq.Migration.TargetClusterId != req.Migration.TargetClusterId {
			continue
		}

		for _, existPair := range existReq.Migration.MigrationTidbGroups {
			for _, newPair := range req.Migration.MigrationTidbGroups {
				if existPair.SourceTidbGroupId == newPair.SourceTidbGroupId {
					return fmt.Errorf("there is an existing privatelink migration (instance %d) for source tidb group '%s'", existIns.ID, existPair.SourceTidbGroupId)
				}
				if existPair.TargetTidbGroupId == newPair.TargetTidbGroupId {
					return fmt.Errorf("there is an existing privatelink migration (instance %d) for target tidb group '%s'", existIns.ID, existPair.TargetTidbGroupId)
				}
			}
		}

	}

	// dry run the request to validate
	_, err = a.comps.CoreSvc.Mgmt.PrivateLinkServiceMigration.CreatePrivateLinkServiceMigration(ctx, req)
	if err != nil {
		return fmt.Errorf("validate failed: %w", err)
	}

	return nil
}

func (a *MigrateClusterPrivateLink) Start(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	req, err := a.getParam(ctx, instance)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}

	plm, err := a.comps.CoreSvc.Mgmt.PrivateLinkServiceMigration.CreatePrivateLinkServiceMigration(ctx, req)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}

	return actions.RunningActionResult("created privatelink migration", map[opent.OutputName]string{
		opent.OutputPrivateLinkMigrationID:                strconv.FormatUint(plm.Id, 10),
		opent.OutputPrivateLinkMigrationTargetServicePlan: plm.TargetServicePlan.String(),
	}), nil
}

func (a *MigrateClusterPrivateLink) Result(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	plmID, targetServicePlan, err := a.getMigrationIdentity(instance)
	if err != nil {
		return actions.NewActionResult(opent.ActionStatusUnknown, err.Error(), nil), nil
	}

	plm, err := a.comps.CoreSvc.Mgmt.PrivateLinkServiceMigration.GetPrivateLinkServiceMigration(ctx, &mgmtv1.GetPrivateLinkServiceMigrationRequest{
		Id:                plmID,
		TargetServicePlan: targetServicePlan,
	})
	if err != nil {
		return actions.RunningActionResult(fmt.Sprintf("get privatelink migration %d failed: %s", plmID, err), nil), nil
	}

	switch plm.State {
	case mgmtv1.PrivateLinkServiceMigration_SUCCEEDED:
		return actions.SucceededActionResult(nil), nil
	case mgmtv1.PrivateLinkServiceMigration_FAILED, mgmtv1.PrivateLinkServiceMigration_CANCELLED:
		return actions.FailedActionResult(fmt.Errorf("privatelink migration state is '%s'", plm.State.String())), nil
	}

	return actions.RunningActionResult(fmt.Sprintf("privatelink migration state is '%s'", plm.State.String()), nil), nil
}

func (a *MigrateClusterPrivateLink) OnCancel(ctx context.Context, instance *opent.Instance) (bool, error) {
	plmID, targetServicePlan, err := a.getMigrationIdentity(instance)
	if err != nil {
		return false, err
	}

	plm, err := a.comps.CoreSvc.Mgmt.PrivateLinkServiceMigration.GetPrivateLinkServiceMigration(ctx, &mgmtv1.GetPrivateLinkServiceMigrationRequest{
		Id:                plmID,
		TargetServicePlan: targetServicePlan,
	})
	if err != nil {
		return false, fmt.Errorf("get privatelink migration %d failed: %s", plmID, err)
	}

	if plm.State == mgmtv1.PrivateLinkServiceMigration_RUNNING {
		_, err = a.comps.CoreSvc.Mgmt.PrivateLinkServiceMigration.CancelPrivateLinkServiceMigration(ctx, &mgmtv1.CancelPrivateLinkServiceMigrationRequest{
			Id:                plmID,
			TargetServicePlan: targetServicePlan,
		})
		if err != nil {
			return true, fmt.Errorf("cancel privatelink migration %d failed: %s", plmID, err)
		}
	}

	return false, nil
}

func (a *MigrateClusterPrivateLink) getParam(ctx context.Context, instance *opent.Instance) (*mgmtv1.CreatePrivateLinkServiceMigrationRequest, error) {
	data := instance.Params[opent.ParamMigratePrivateLinkRequest]
	if data == "" {
		return nil, fmt.Errorf("missing required param")
	}

	req := &mgmtv1.CreatePrivateLinkServiceMigrationRequest{
		Migration: &mgmtv1.PrivateLinkServiceMigration{},
	}
	err := protojson.Unmarshal([]byte(data), req)
	if err != nil {
		return nil, fmt.Errorf("unmarshal param failed: %w", err)
	}

	return req, nil
}

func (a *MigrateClusterPrivateLink) getMigrationIdentity(instance *opent.Instance) (uint64, v1beta1.ServicePlan, error) {
	plmIDStr, ok := instance.GetOutput(opent.OutputPrivateLinkMigrationID)
	if !ok || plmIDStr == "" {
		return 0, 0, fmt.Errorf("missing output '%s'", opent.OutputPrivateLinkMigrationID)
	}

	plmID, err := strconv.ParseUint(plmIDStr, 10, 64)
	if err != nil {
		return 0, 0, fmt.Errorf("invalid output '%s': %s", opent.OutputPrivateLinkMigrationID, err)
	}

	targetServicePlanStr, ok := instance.GetOutput(opent.OutputPrivateLinkMigrationTargetServicePlan)
	if !ok || targetServicePlanStr == "" {
		return 0, 0, fmt.Errorf("missing output '%s'", opent.OutputPrivateLinkMigrationTargetServicePlan)
	}

	targetServicePlanValue, ok := v1beta1.ServicePlan_value[targetServicePlanStr]
	if !ok {
		return 0, 0, fmt.Errorf("invalid output '%s': unknown service plan '%s'", opent.OutputPrivateLinkMigrationTargetServicePlan, targetServicePlanStr)
	}

	return plmID, v1beta1.ServicePlan(targetServicePlanValue), nil
}
