package mailruntime

import (
	"fmt"
	"strconv"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/protocol"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/token"
)

const timeFormat = time.RFC3339

func (service *Service) tokenBinding(
	runtime runtimeContext,
	kind string,
	action string,
) token.Binding {
	return token.Binding{
		Kind:           kind,
		WorkspaceID:    runtime.invocation.WorkspaceID,
		InstallationID: runtime.invocation.ServiceInstallationID,
		ConnectionID:   runtime.invocation.ServiceConnectionID,
		Generation:     runtime.material.Connection.Generation,
		AgentID:        runtime.invocation.AgentID,
		Action:         action,
	}
}

func (service *Service) sealReference(
	runtime runtimeContext,
	kind string,
	action string,
	ref protocol.MessageRef,
	revision uint64,
	part string,
) (string, error) {
	binding := service.tokenBinding(runtime, kind, action)
	encoded, err := service.tokens.Seal(
		token.Claims{
			Kind:           binding.Kind,
			WorkspaceID:    binding.WorkspaceID,
			InstallationID: binding.InstallationID,
			ConnectionID:   binding.ConnectionID,
			Generation:     binding.Generation,
			AgentID:        binding.AgentID,
			Action:         binding.Action,
			Mailbox:        ref.Mailbox,
			UIDValidity:    ref.UIDValidity,
			UID:            ref.UID,
			Revision:       revision,
			Part:           part,
		},
		service.tokenTTL,
	)
	if err != nil {
		return "", mailError(
			ErrorInternal,
			"mail reference could not be created",
			err,
		)
	}
	return encoded, nil
}

func (service *Service) openReference(
	encoded string,
	runtime runtimeContext,
	kind string,
	action string,
) (protocol.MessageRef, token.Claims, error) {
	claims, err := service.tokens.Open(
		encoded,
		service.tokenBinding(runtime, kind, action),
	)
	if err != nil {
		return protocol.MessageRef{}, token.Claims{},
			mailError(
				ErrorValidation,
				"mail reference is invalid or expired",
				err,
			)
	}
	return protocol.MessageRef{
		Mailbox:     claims.Mailbox,
		UIDValidity: claims.UIDValidity,
		UID:         claims.UID,
	}, claims, nil
}

func (service *Service) sealSearchCursor(
	runtime runtimeContext,
	beforeUID uint32,
	filterDigest string,
	limit int,
) (string, error) {
	if beforeUID == 0 {
		return "", nil
	}
	binding := service.tokenBinding(
		runtime,
		token.KindCursor,
		actionSearchMessages,
	)
	encoded, err := service.tokens.Seal(
		token.Claims{
			Kind:           binding.Kind,
			WorkspaceID:    binding.WorkspaceID,
			InstallationID: binding.InstallationID,
			ConnectionID:   binding.ConnectionID,
			Generation:     binding.Generation,
			AgentID:        binding.AgentID,
			Action:         binding.Action,
			Cursor:         strconv.FormatUint(uint64(beforeUID), 10),
			FilterDigest:   filterDigest,
			Limit:          limit,
		},
		service.cursorTTL,
	)
	if err != nil {
		return "", mailError(
			ErrorInternal,
			"mail search cursor could not be created",
			err,
		)
	}
	return encoded, nil
}

func (service *Service) openSearchCursor(
	encoded string,
	runtime runtimeContext,
	filterDigest string,
	limit int,
) (uint32, error) {
	claims, err := service.tokens.Open(
		encoded,
		service.tokenBinding(
			runtime,
			token.KindCursor,
			actionSearchMessages,
		),
	)
	if err != nil ||
		claims.FilterDigest != filterDigest ||
		claims.Limit != limit {
		return 0, mailError(
			ErrorValidation,
			"mail search cursor is invalid or expired",
			err,
		)
	}
	value, err := strconv.ParseUint(claims.Cursor, 10, 32)
	if err != nil || value == 0 {
		return 0, mailError(
			ErrorValidation,
			"mail search cursor is invalid or expired",
			err,
		)
	}
	return uint32(value), nil
}

func validateDraftRef(ref protocol.DraftRef) error {
	if ref.Mailbox == "" ||
		ref.UIDValidity == 0 ||
		ref.UID == 0 {
		return fmt.Errorf("draft reference is incomplete")
	}
	return nil
}
