// Package service add by blacknc <blacknc@163.com> at 2023/04/06 15:38:19
package service

import (
	"context"
	comerrs "dataservices/commons/errs"
	"dataservices/commons/eventbus"
	"dataservices/ddb"
	dto "dataservices/internal/dto/dataapi"
	"dataservices/internal/repository"
	"dataservices/internal/repository_ddb"

	"go.uber.org/zap"
)

func init() {
	registerHandler(
		eventbus.HandlerConfig{
			EventType:  dto.BusEventDataapp,
			DataStruct: dto.DataappEvent{},
			HandleFunc: handleDataappEvent,
		},
	)
	registerHandler(
		eventbus.HandlerConfig{
			EventType:  dto.BusEventDataappDeploy,
			DataStruct: dto.DataappDeployEvent{},
			HandleFunc: handleDataappDeployEvent,
		},
	)
}

func handleDataappEvent(ctx context.Context, logger *zap.Logger, event *eventbus.Event) (err error) {
	logger.Info("handleDataappEvent begin...")
	defer func() {
		logger.Info("handleDataappEvent end", zap.Error(err))
	}()

	dataappEvent, ok := event.Data.(*dto.DataappEvent)
	if !ok || dataappEvent == nil || dataappEvent.DataappID == 0 {
		logger.Error("got invalid dataapp event", zap.Any("event", event))
		return comerrs.ErrInvalidEvent
	}

	switch dataappEvent.ActionType {
	case dto.DataappActionCreate:
		err = handleDataappCreateEvent(ctx, logger, dataappEvent)
	case dto.DataappActionDelete:
		err = handleDataappDeleteEvent(ctx, logger, dataappEvent)
	case dto.DataappActionUpdateLogResSwitch:
		err = handleDataappChangeLogResSwitchEvent(ctx, logger, dataappEvent)
	default:
		logger.Error("dataapp event got unsupported action", zap.String("action_type", dataappEvent.ActionType))
		err = comerrs.ErrInvalidEvent
	}
	return err
}

func handleDataappCreateEvent(ctx context.Context, logger *zap.Logger, dataappEvent *dto.DataappEvent) error {
	var (
		err       error
		dataappID = dataappEvent.DataappID
	)

	appInfo, err := repository.GetDataApp(dataappID)
	if err != nil {
		return err
	}

	ddbClient := ddb.Client()

	// save dataapp
	if err = repository_ddb.PutDataApp(ctx, ddbClient, appInfo); err != nil {
		return err
	}

	// save endpoint
	endpointList, err := repository.ListEndpoints(ctx, dataappID)
	if err != nil {
		return err
	}
	if len(endpointList) > 0 {
		written, err := repository_ddb.BatchPutEndpoint(ctx, ddbClient, endpointList)
		if err != nil {
			return err
		} else if written != len(endpointList) {
			logger.Error("BatchPutEndpoint partial success", zap.Int("total", len(endpointList)), zap.Int("written", written))
		}
	}

	// save deployed endpoint
	deployedEndpointList, err := repository.ListAppPublishedEndpoint(ctx, dataappID)
	if err != nil {
		return err
	}
	if len(deployedEndpointList) > 0 {
		written, err := repository_ddb.BatchPutPublishedEndpoint(ctx, ddbClient, deployedEndpointList)
		if err != nil {
			return err
		} else if written != len(deployedEndpointList) {
			logger.Error("BatchPutPublishedEndpoint partial success", zap.Int("total", len(deployedEndpointList)), zap.Int("written", written))
		}
	}

	return nil
}

func handleDataappDeleteEvent(ctx context.Context, logger *zap.Logger, dataappEvent *dto.DataappEvent) error {
	var (
		err       error
		dataappID = dataappEvent.DataappID
	)

	ddbClient := ddb.Client()

	// delete dataapp
	if err = repository_ddb.DeleteDataApp(ctx, ddbClient, dataappID); err != nil {
		return err
	}

	// delete endpoint
	if err = repository_ddb.DeleteEndpointByAppID(ctx, ddbClient, dataappID); err != nil {
		return err
	}

	// delete published endpoint
	if err = repository_ddb.DeletePublishedEndpointByAppID(ctx, ddbClient, dataappID); err != nil {
		return err
	}

	// delete linked cluster
	if err = repository_ddb.DeleteAppClusterByAppID(ctx, ddbClient, dataappID); err != nil {
		return err
	}
	return nil
}

func handleDataappDeployEvent(ctx context.Context, logger *zap.Logger, event *eventbus.Event) (err error) {
	logger.Info("handleDataappDeployEvent begin...")
	defer func() {
		logger.Info("handleDataappDeployEvent end", zap.Error(err))
	}()

	deployEvent, ok := event.Data.(*dto.DataappDeployEvent)
	if !ok || deployEvent == nil || deployEvent.DataappID == 0 {
		logger.Error("got invalid dataapp deploy event", zap.Any("event", event))
		return comerrs.ErrInvalidEvent
	}

	dataAPP, err := repository.GetDataApp(deployEvent.DataappID)
	if err != nil {
		return err
	}

	ddbClient := ddb.Client()

	// save dataapp
	if err = repository_ddb.PutDataApp(ctx, ddbClient, dataAPP); err != nil {
		logger.Error("PutDataApp faled", zap.Error(err))
		return err
	}

	endpointIDs := append(deployEvent.EndpointRequest.AddIDs, deployEvent.EndpointRequest.ModifyIDs...)
	if len(endpointIDs) > 0 {
		endpointList, err := repository.ListEndpointByIDs(ctx, endpointIDs)
		if err != nil {
			logger.Error("ListEndpointByIDs for draft failed", zap.Error(err))
			return err
		}
		if written, err := repository_ddb.BatchPutEndpoint(ctx, ddbClient, endpointList); err != nil {
			logger.Error("BatchPutEndpoint failed", zap.Int("total", len(endpointIDs)), zap.Error(err))
			return err
		} else if written != len(endpointIDs) {
			logger.Warn("BatchPutEndpoint only partially succeeded", zap.Int("total", len(endpointIDs)), zap.Int("written", written))
		} else {
			logger.Info("BatchPutEndpoint all succeeded", zap.Int("total", len(endpointIDs)), zap.Int("written", written))
		}
	}

	delEndpointIDs := deployEvent.EndpointRequest.DeleteIDs
	if len(delEndpointIDs) > 0 {
		if written, err := repository_ddb.BatchDeleteEndpoint(ctx, ddbClient, delEndpointIDs); err != nil {
			logger.Error("BatchDeleteEndpoint failed", zap.Int("total", len(delEndpointIDs)), zap.Error(err))
			return err
		} else if written != len(delEndpointIDs) {
			logger.Warn("BatchDeleteEndpoint only partially succeeded", zap.Int("total", len(delEndpointIDs)), zap.Int("written", written))
		} else {
			logger.Info("BatchDeleteEndpoint all succeeded", zap.Int("total", len(delEndpointIDs)), zap.Int("written", written))
		}
	}

	undeployIDs := deployEvent.EndpointRequest.UndeployIDs
	if len(undeployIDs) > 0 {
		if written, err := repository_ddb.BatchDeletePublishedEndpoint(ctx, ddbClient, undeployIDs); err != nil {
			logger.Error("BatchDeletePublishedEndpoint failed", zap.Int("total", len(undeployIDs)), zap.Error(err))
			return err
		} else if written != len(undeployIDs) {
			logger.Warn("BatchDeletePublishedEndpoint only partially succeeded", zap.Int("total", len(undeployIDs)), zap.Int("written", written))
		} else {
			logger.Info("BatchDeletePublishedEndpoint all succeeded", zap.Int("total", len(undeployIDs)), zap.Int("written", written))
		}
	}

	deployIDs := deployEvent.EndpointRequest.DeployIDs
	if len(deployIDs) > 0 {
		publishedEndpoints, err := repository.ListPublishedEndpointByIDs(ctx, deployIDs)
		if err != nil {
			logger.Error("ListPublishedEndpointByIDs failed", zap.Error(err))
			return err
		}
		if written, err := repository_ddb.BatchPutPublishedEndpoint(ctx, ddbClient, publishedEndpoints); err != nil {
			logger.Error("BatchPutPublishedEndpoint failed", zap.Int("total", len(deployIDs)), zap.Error(err))
			return err
		} else if written != len(deployIDs) {
			logger.Warn("BatchPutPublishedEndpoint only partially succeeded", zap.Int("total", len(deployIDs)), zap.Int("written", written))
		} else {
			logger.Info("BatchPutPublishedEndpoint all succeeded", zap.Int("total", len(deployIDs)), zap.Int("written", written))
		}
	}

	delClusterIDs := deployEvent.ClusterRequest.DeleteIDs
	if len(delClusterIDs) > 0 {
		publishAppClusterEvent(ctx, deployEvent.DataappID, delClusterIDs, dataAPP.CreatedBy, dto.DataAppBindClusterActionDelCluster)
	}

	addClusterIDs := deployEvent.ClusterRequest.AddIDs
	if len(addClusterIDs) > 0 {
		publishAppClusterEvent(ctx, deployEvent.DataappID, addClusterIDs, dataAPP.CreatedBy, dto.DataAppBindClusterActionAddCluster)
	}

	return nil
}

func handleDataappChangeLogResSwitchEvent(ctx context.Context, logger *zap.Logger, dataappEvent *dto.DataappEvent) error {
	var (
		err       error
		dataappID = dataappEvent.DataappID
	)

	appInfo, err := repository.GetDataApp(dataappID)
	if err != nil {
		logger.Error("UpdateDataAppSetting GetDataApp failed", zap.Uint64("dataapp", dataappID), zap.Error(err))
		return err
	}

	ddbClient := ddb.Client()

	// save changes
	if err = repository_ddb.UpdateDataAppSetting(ctx, ddbClient, dataappID, &appInfo.Settings); err != nil {
		logger.Error("UpdateDataAppSetting set ddb failed", zap.Uint64("dataapp", dataappID), zap.Error(err))
		return err
	}

	return nil
}
