package br

import (
	"context"
	"encoding/json"
	"strconv"
	"strings"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/timestamppb"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc"
)

// RestoreStatus implements Service.
func (s *serviceImpl) RestoreStatus(
	ctx context.Context,
	req *globalngv1.RestoreStatusRequest,
) (*globalngv1.RestoreStatusResponse, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "Domain.RestoreStatus")
	defer span.End()

	clusterId, err := strconv.ParseUint(req.ClusterId, 10, 64)
	if err != nil {
		return nil, bizerr.WrapError(err, "failed to parse cluster id")
	}

	restore, berr := s.restoreRepo.Get(ctx, clusterId)
	if berr != nil {
		return nil, berr.Chain("failed to get restore")
	}

	jsonBytes, _ := json.Marshal(restore)
	tilog.Info(ctx, "get restore status", zap.String("cluster id", req.ClusterId), zap.String("restore", grpc.RedactSecrets(string(jsonBytes))))

	var resp *globalngv1.RestoreStatusResponse
	switch restore.State {
	case "pending":
		resp = &globalngv1.RestoreStatusResponse{
			State:    globalngv1.RestoreState_RESTORE_STATE_PENDING,
			Message:  "Restore is pending for provisioning resources",
			Progress: 0,
		}
	case "running":
		restoreAdapter, berr := s.getRestoreAdapter(ctx, nil, restore)
		if berr != nil {
			return nil, berr.Chain("failed to get restore adapter")
		}
		restoreStatus, berr := restoreAdapter.GetRestoreRunningStatus(ctx, clusterId, restore)
		if berr != nil {
			return nil, berr.Chain("failed to get restore running status")
		}
		resp = restoreStatus
	case "success":
		resp = &globalngv1.RestoreStatusResponse{
			State:    globalngv1.RestoreState_RESTORE_STATE_SUCCESS,
			Message:  "Restore is succeeded",
			Progress: 100,
		}
	case "failed":
		resp = &globalngv1.RestoreStatusResponse{
			State:   globalngv1.RestoreState_RESTORE_STATE_FAILED,
			Message: "Restore is failed",
		}
	case "cancelled":
		resp = &globalngv1.RestoreStatusResponse{
			State:   globalngv1.RestoreState_RESTORE_STATE_CANCELLED,
			Message: "Restore is cancelled",
		}
	default:
		return nil, bizerr.New(bizerr.GeneralInternalErrorCode, "invalid restore state")
	}

	return restoreStatusWithDetails(resp, restore), nil
}

func restoreStatusWithDetails(
	status *globalngv1.RestoreStatusResponse,
	restore *entity.Restore,
) *globalngv1.RestoreStatusResponse {
	details := &globalngv1.RestoreDetails{
		SourceClusterId:              restore.SourceClusterId,
		RestoreMode:                  convertRestoreModeToProto(restore.RestoreMode),
		BackupId:                     restore.BackupId,
		RestoreRequestedTime:         timestamppb.New(restore.CreatedAt),
		RestoreUpdatedTime:           timestamppb.New(restore.UpdatedAt),
		ExpectedRestoreDataSizeBytes: restore.ExpectRestoreDataSize,
		SourceRegionId:               restore.SourceRegionId,
		SourceCloudProvider: globalcommonv1.CloudProvider_Name(
			globalcommonv1.CloudProvider_Name_value[strings.ToUpper(restore.SourceCloudProvider)],
		),
		SourceServicePlan: globalcommonv1.ServicePlan(
			globalcommonv1.ServicePlan_value[restore.SourceServicePlan],
		),
	}
	if restore.PointInTime != nil {
		details.PointInTime = timestamppb.New(*restore.PointInTime)
	}
	status.Details = details
	return status
}

func convertRestoreModeToProto(mode schema.RestoreMode) globalngv1.RestoreMode {
	switch mode {
	case schema.RestoreModeSnapshot:
		return globalngv1.RestoreMode_RESTORE_MODE_SNAPSHOT
	case schema.RestoreModePitr:
		return globalngv1.RestoreMode_RESTORE_MODE_PITR
	case schema.RestoreModeDedicatedSnapshot:
		return globalngv1.RestoreMode_RESTORE_MODE_DEDICATED_SNAPSHOT
	case schema.RestoreModeClassicSnapshot:
		return globalngv1.RestoreMode_RESTORE_MODE_CLASSIC_SNAPSHOT
	default:
		return globalngv1.RestoreMode_RESTORE_MODE_UNSPECIFIED
	}
}
