package eventbus

import (
	"context"
	"time"

	"github.com/pingcap/errors"
	core_event "github.com/tidbcloud/core-svc/event"
	"github.com/tidbcloud/ecosystem-service/api/v1/vercel"
	"github.com/tidbcloud/ecosystem-service/internal/metric"
	vercelDB "github.com/tidbcloud/ecosystem-service/internal/storage/db/vercel"
	"github.com/tidbcloud/ecosystem-service/internal/utils"
	"github.com/tidbcloud/pkgv2/log"
	"github.com/tidbcloud/pkgv2/tracing"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/transport/tieventbus"
	"go.uber.org/zap"
)

type ClusterEventService struct {
	eventbusService      *EventbusService
	clusterConnectionDAO vercelDB.ClusterConnectionDAO
	clusterLogic         *vercel.ClusterConnLogic
	composeEntityDAO     vercelDB.ComposeEntityDAO
}

func NewClusterEventService(eventbusService *EventbusService,
	clusterConnectionDAO vercelDB.ClusterConnectionDAO,
	clusterLogic *vercel.ClusterConnLogic,
	composeEntityDAO vercelDB.ComposeEntityDAO,
) *ClusterEventService {
	s := &ClusterEventService{
		eventbusService:      eventbusService,
		clusterConnectionDAO: clusterConnectionDAO,
		clusterLogic:         clusterLogic,
		composeEntityDAO:     composeEntityDAO,
	}
	s.registerHandler()
	return s
}

func (s *ClusterEventService) registerHandler() {
	s.eventbusService.RegisterHandler(tieventbus.HandlerConfig{
		EventType:  "ClusterEvent",
		Group:      "cluster-consumer-vercel",
		RetryTimes: 5,
		HandleFunc: s.handleFunc,
		DataStruct: core_event.ClusterEvent{},
	})
}

func (s *ClusterEventService) handleFunc(e *tieventbus.Event) (err error) {
	ctx := context.Background()
	ctx, span := Tracer.Start(ctx, "ClusterEventService.handleFunc")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	logger := log.WithContextL(ctx).With(zap.String("event_id", e.ID)).With(zap.String("event_type", e.Type))
	logger.Info("event received")
	evt, ok := e.Data.(*core_event.ClusterEvent)
	if !ok {
		logger.Warn("event type mismatched")
		return nil
	}
	if !evt.IsDevTier {
		return nil
	}

	if evt.EventType == core_event.ClusterEventTypeDeleted {
		return s.handleDeleteCluster(ctx, e.ID, evt, logger)
	}

	return nil
}

func (s *ClusterEventService) handleDeleteCluster(ctx context.Context, eventID string, evt *core_event.ClusterEvent, logger *zap.Logger) (err error) {
	logger = logger.With(zap.Uint64("cluster_id", evt.ClusterID))
	logger.Info("handle delete cluster event", zap.Any("event", evt))
	defer func() {
		status := metric.StatusOK
		if err != nil {
			status = metric.StatusErr
		}

		metric.EventHandlerCounter.WithLabelValues(eventID, string(evt.EventType), status).Inc()
	}()

	conns, err := s.clusterConnectionDAO.ListVercelConnectionsByClusterID(ctx, evt.ClusterID)
	if err != nil {
		logger.Error("failed to list connections", zap.Error(err))
		return err
	}

	if len(conns) == 0 {
		logger.Info("no connections linked with the deleted cluster, skip")
		return nil
	}

	tilog.Info(ctx, "Start to delete environments")
	connIDs, successConnectionIDs, err := s.deleteVercelEnvsByCluster(ctx, evt.ClusterID)
	if err != nil {
		tilog.Error(ctx, "failed to delete environments", zap.Error(err))
		return err
	}

	err = s.composeEntityDAO.DeleteClusterConnectionsByIDs(ctx, successConnectionIDs)
	if err != nil {
		tilog.Error(ctx, "failed to clean connections", zap.Error(err))
		return err
	}

	if len(connIDs) > len(successConnectionIDs) {
		tilog.Error(ctx, "failed to delete some connections", zap.Any("connectionIDs", connIDs), zap.Any("successConnectionIDs", successConnectionIDs))
		return errors.New("failed to delete some connections")
	}
	tilog.Info(ctx, "Success to delete connections", zap.Any("successConnectionIDs", successConnectionIDs))
	return nil
}

func (s *ClusterEventService) deleteVercelEnvsByCluster(ctx context.Context, clusterID uint64) ([]uint64, []uint64, error) {
	connections, err := s.clusterConnectionDAO.ListVercelConnectionsByClusterID(ctx, clusterID)
	if err != nil {
		tilog.Error(ctx, "failed to list connections", zap.Error(err))
		return nil, nil, err
	}

	connectionsIDs := make([]uint64, len(connections))
	var successConnectionIDs []uint64
	// In case of rate limit, we need to delete environments one by one
	// If one task failed, we don't need to stop the whole process, just log the error and continue
	for i, connection := range connections {
		time.Sleep(1 * time.Second)
		succeed := true
		if err := s.clusterLogic.DeleteEnvsByEnvIdentifier(ctx, connection.VercelProjectID, utils.GetTeamIDString(connection.VercelIntegrationConfiguration.TeamID), connection.EnvironmentIdentifier, connection.VercelIntegrationConfiguration.ApiAccessToken); err != nil {
			tilog.Error(ctx, "failed to delete environment variables", zap.Any("connection", connection), zap.Error(err))
			succeed = false
		}

		for _, identifier := range connection.BranchEnvironmentIdentifiers {
			err := s.clusterLogic.DeleteBranchEnvsByEnvIdentifier(ctx, connection.VercelProjectID, utils.GetTeamIDString(connection.VercelIntegrationConfiguration.TeamID), identifier, connection.VercelIntegrationConfiguration.ApiAccessToken)
			if err != nil {
				tilog.Error(ctx, "failed to delete branch environment variables", zap.Any("connection", connection), zap.Error(err))
				succeed = false
			}
		}

		if succeed {
			successConnectionIDs = append(successConnectionIDs, connection.ID)
		}
		connectionsIDs[i] = connection.ID
	}

	return connectionsIDs, successConnectionIDs, nil
}
