package authactor

import (
	"fmt"
	"time"

	"github.com/tidbcloud/devops/entity/event"
	jmpent "github.com/tidbcloud/devops/entity/jump"
	"github.com/tidbcloud/devops/pkg/infra/errs"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	jmpsvcapi "github.com/tidbcloud/devops/pkg/service/jump/api"
	"github.com/tidbcloud/devops/pkg/service/jump/jumppod"
	"github.com/tidbcloud/devops/pkg/service/jump/jumppod/types"
)

type actorWhenCreating struct {
	*baseActor
}

func (a *actorWhenCreating) ContinueIfPodReady() error {
	ctx := a.Context
	ins := a.JumpAuthorization

	pod, err := a.JumpPodManager.GetJumpPod(ctx, EmailToJumpPodName(ins.Applicant))
	if jumppod.IsNotFound(err) {
		_, err := a.JumpPodManager.SetUpJumpPod(ctx, EmailToJumpPodName(ins.Applicant))
		if err != nil {
			return fmt.Errorf("SetUpJumpPod() failed: %w", err)
		}
		return errs.RetriableErrorf("created a new jump pod, retrying")
	}
	if err != nil {
		return fmt.Errorf("GetJumpPod() failed: %w", err)
	}

	err = pod.IsReady(ctx)
	if err != nil {
		return fmt.Errorf("pod.IsReady() returns error: %w", err)
	}

	// Set up Jump
	param, err := GenJumpParam(ins)
	if err != nil {
		return fmt.Errorf("fail to generate jump param on genJumpParam(): %w", err)
	}

	updatedJumps := []*jmpent.Jump{}
	for _, jump := range ins.Jumps {
		updatedJumps = append(updatedJumps, jump)

		jumpParam, err := pod.PrepareJump(ctx, jump.Destination.Extract(), param)
		if err != nil {
			return fmt.Errorf("prepare jump for destination '%s' returns error: %w", jump.Destination.Extract().ID(), err)
		}
		jump.Method = jumpParam.Method
		jump.Attributes = jumpParam.Attr
	}

	// Prepare Client
	genAKParam := &types.GenerateAccessTokenParam{
		GenerateTokenParam: types.GenerateTokenParam{
			ExpireAt: time.Now().Add(*a.Conf.SyncJumpPodInterval).Add(time.Hour), // TODO: use db to record the expire time
		},
	}
	accessToken, err := pod.GenerateAccessToken(ctx, genAKParam)
	if err != nil {
		return fmt.Errorf("pod failed to generate access token: %w", err)
	}

	err = a.JumpServerCli.UpdateAccountToken(ctx, ins.Applicant, accessToken.Data)
	if err != nil {
		return fmt.Errorf("update user '%s' token failed: %w", ins.Applicant, err)
	}

	return a.PatchAuthorization(ctx, func(current *jmpent.Authorization) error {
		current.Phase = jmpent.AuthorizationPhaseCreated
		current.ExpireAt = pointer.Pointer(param.ExpireAt.Unix())
		current.Jumps = updatedJumps
		return nil
	})

}

func GenJumpParam(auth *jmpent.Authorization) (*types.PrepareJumpParam, error) {
	param := &types.PrepareJumpParam{
		Privilege:           auth.Privilege,
		EnableGatewayAccess: auth.EnableGatewayAccess,
	}

	startTime := time.Now()
	switch auth.Expiration {
	case jmpent.JumpExpiration1Hour:
		param.ExpireAt = startTime.Add(time.Hour)
	case jmpent.JumpExpiration3Hour:
		param.ExpireAt = startTime.Add(3 * time.Hour)
	case jmpent.JumpExpiration6Hour:
		param.ExpireAt = startTime.Add(6 * time.Hour)
	case jmpent.JumpExpiration12Hour:
		param.ExpireAt = startTime.Add(12 * time.Hour)
	case jmpent.JumpExpiration24Hour:
		param.ExpireAt = startTime.Add(24 * time.Hour)
	case jmpent.JumpExpiration3Day:
		param.ExpireAt = startTime.Add(72 * time.Hour)
	case jmpent.JumpExpiration7Day:
		param.ExpireAt = startTime.Add(168 * time.Hour)
	case jmpent.JumpExpiration30Day:
		param.ExpireAt = startTime.Add(720 * time.Hour)
	default:
		return nil, fmt.Errorf("unsupported expiration type %s", auth.Expiration)
	}
	return param, nil
}

func (a *actorWhenCreating) Cancel(param jmpsvcapi.CancelAuthorizaitonParam) error {
	ctx := a.Context

	err := a.finalizeJumps(ctx, param.Force, time.Second*time.Duration(param.Timeout))
	if err != nil {
		return err
	}

	a.EventDetail = event.JumpEventCancel{
		Force: param.Force,
	}

	// transition status to 'Cancel'
	return a.PatchAuthorization(ctx,
		func(current *jmpent.Authorization) error {
			current.Phase = jmpent.AuthorizationPhaseCanceled
			current.ClosedAt = pointer.Pointer(time.Now().Unix())
			return nil
		})
}
