// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.

//go:build !oss
// +build !oss

package manager

import (
	"context"
	"strings"
	"time"

	"golang.org/x/xerrors"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/gitpod-io/gitpod/common-go/kubernetes"
	wsk8s "github.com/gitpod-io/gitpod/common-go/kubernetes"
	"github.com/gitpod-io/gitpod/common-go/log"
	"github.com/gitpod-io/gitpod/common-go/tracing"
	wsdaemon "github.com/gitpod-io/gitpod/ws-daemon/api"
	"github.com/gitpod-io/gitpod/ws-manager/api"
)

// TakeSnapshot creates a copy of the workspace content and stores it so that another workspace can be created from it.
func (m *Manager) TakeSnapshot(ctx context.Context, req *api.TakeSnapshotRequest) (res *api.TakeSnapshotResponse, err error) {
	if m.Config.MaintenanceMode {
		return &api.TakeSnapshotResponse{}, status.Error(codes.FailedPrecondition, "under maintenance mode")
	}

	span, ctx := tracing.FromContext(ctx, "TakeSnapshot")
	tracing.ApplyOWI(span, log.OWI("", "", req.Id))
	defer tracing.FinishSpan(span, &err)

	pod, err := m.findWorkspacePod(ctx, req.Id)
	if isKubernetesObjNotFoundError(err) {
		return nil, status.Errorf(codes.NotFound, "workspace %s does not exist", req.Id)
	}
	if err != nil {
		return nil, status.Errorf(codes.Internal, "cannot get workspace status: %q", err)
	}
	tracing.ApplyOWI(span, wsk8s.GetOWIFromObject(&pod.ObjectMeta))
	span.LogKV("event", "get pod")

	wso, err := m.getWorkspaceObjects(ctx, pod)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "cannot get workspace status: %q", err)
	}

	sts, err := m.getWorkspaceStatus(*wso)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "cannot get workspace status: %q", err)
	}

	if sts.Phase != api.WorkspacePhase_RUNNING {
		return nil, status.Errorf(codes.FailedPrecondition, "can only take snapshots of running workspaces")
	}

	sync, err := m.connectToWorkspaceDaemon(ctx, workspaceObjects{Pod: pod})
	if err != nil {
		return nil, status.Errorf(codes.Unavailable, "cannot connect to workspace daemon: %q", err)
	}

	r, err := sync.TakeSnapshot(ctx, &wsdaemon.TakeSnapshotRequest{
		Id:                req.Id,
		ReturnImmediately: req.ReturnImmediately,
	})
	if err != nil {
		// err is already a grpc error - no need to faff with that
		return nil, err
	}

	return &api.TakeSnapshotResponse{Url: r.Url}, nil
}

// ControlAdmission makes a workspace accessible for everyone or for the owner only
func (m *Manager) ControlAdmission(ctx context.Context, req *api.ControlAdmissionRequest) (res *api.ControlAdmissionResponse, err error) {
	//nolint:ineffassign
	span, ctx := tracing.FromContext(ctx, "ControlAdmission")
	tracing.ApplyOWI(span, log.OWI("", "", req.Id))
	tracing.LogRequestSafe(span, req)
	defer tracing.FinishSpan(span, &err)

	pod, err := m.findWorkspacePod(ctx, req.Id)
	if isKubernetesObjNotFoundError(err) {
		return nil, status.Errorf(codes.NotFound, "workspace %s does not exist", req.Id)
	}
	if err != nil {
		return nil, status.Errorf(codes.Internal, "cannot get workspace status: %q", err)
	}
	tracing.ApplyOWI(span, wsk8s.GetOWIFromObject(&pod.ObjectMeta))
	span.LogKV("event", "get pod")

	wso, err := m.getWorkspaceObjects(ctx, pod)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "cannot get workspace status: %q", err)
	}

	sts, err := m.getWorkspaceStatus(*wso)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "cannot get workspace status: %q", err)
	}

	if sts.Phase == api.WorkspacePhase_STOPPING || sts.Phase == api.WorkspacePhase_STOPPED {
		return nil, status.Errorf(codes.FailedPrecondition, "cannot control admission of stopping workspaces")
	}

	val, ok := api.AdmissionLevel_name[int32(req.Level)]
	if !ok {
		return nil, status.Errorf(codes.InvalidArgument, "invalid admission level")
	}
	// lowercase is just for vanity's sake
	val = strings.ToLower(val)

	err = m.markWorkspace(ctx, req.Id, addMark(kubernetes.WorkspaceAdmissionAnnotation, val))
	if err != nil {
		return nil, status.Errorf(codes.Internal, "cannot change workspace admission level: %q", err)
	}

	return &api.ControlAdmissionResponse{}, nil
}

// SetTimeout changes the default timeout for a running workspace
func (m *Manager) SetTimeout(ctx context.Context, req *api.SetTimeoutRequest) (res *api.SetTimeoutResponse, err error) {
	//nolint:ineffassign
	span, ctx := tracing.FromContext(ctx, "SetTimeout")
	tracing.ApplyOWI(span, log.OWI("", "", req.Id))
	defer tracing.FinishSpan(span, &err)

	_, err = time.ParseDuration(req.Duration)
	if err != nil {
		return nil, xerrors.Errorf("invalid duration \"%s\": %w", req.Duration, err)
	}

	err = m.markWorkspace(ctx, req.Id, addMark(customTimeoutAnnotation, req.Duration))
	if err != nil {
		return nil, xerrors.Errorf("cannot set workspace timeout: %w", err)
	}

	return &api.SetTimeoutResponse{}, nil
}

// BackupWorkspace attempts to create a backup of the workspace, ignoring its perceived current status as much as it can
func (m *Manager) BackupWorkspace(ctx context.Context, req *api.BackupWorkspaceRequest) (res *api.BackupWorkspaceResponse, err error) {
	span, ctx := tracing.FromContext(ctx, "BackupWorkspace")
	tracing.ApplyOWI(span, log.OWI("", "", req.Id))
	defer tracing.FinishSpan(span, &err)

	pod, err := m.findWorkspacePod(ctx, req.Id)
	if isKubernetesObjNotFoundError(err) {
		return nil, status.Errorf(codes.NotFound, "workspace pod for %s does not exist", req.Id)
	}
	if err != nil {
		return nil, status.Errorf(codes.Internal, "cannot get workspace pod: %q", err)
	}
	tracing.ApplyOWI(span, wsk8s.GetOWIFromObject(&pod.ObjectMeta))
	span.LogKV("event", "get pod")

	sync, err := m.connectToWorkspaceDaemon(ctx, workspaceObjects{Pod: pod})
	if err != nil {
		return nil, status.Errorf(codes.Unavailable, "cannot connect to workspace daemon: %q", err)
	}

	r, err := sync.BackupWorkspace(ctx, &wsdaemon.BackupWorkspaceRequest{Id: req.Id})
	if err != nil {
		// err is already a grpc error - no need to faff with that
		return nil, err
	}

	return &api.BackupWorkspaceResponse{Url: r.Url}, nil
}
