package client

import (
	"context"
	"crypto/ecdsa"

	buffPool "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/util/pool"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/transformer"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
)

// defaultGRPCPayloadChunkLen default value for maxChunkLen.
// See PrmObjectPutInit.SetGRPCPayloadChunkLen for details.
const defaultGRPCPayloadChunkLen = 3 << 20

// PrmObjectPutInit groups parameters of ObjectPutInit operation.
type PrmObjectPutInit struct {
	XHeaders []string

	BearerToken *bearer.Token

	Session *session.Object

	Local bool

	CopiesNumber []uint32

	MaxChunkLength int

	MaxSize uint64

	EpochSource transformer.EpochSource

	WithoutHomomorphHash bool

	Key *ecdsa.PrivateKey

	Pool *buffPool.BufferPool
}

// SetCopiesNumber sets number of object copies that is enough to consider put successful.
//
// Deprecated: Use PrmObjectPutInit.CopiesNumber instead.
func (x *PrmObjectPutInit) SetCopiesNumber(copiesNumber uint32) {
	x.CopiesNumber = []uint32{copiesNumber}
}

// SetCopiesNumberByVectors sets ordered list of minimal required object copies numbers
// per placement vector. List's length MUST equal container's placement vector number,
// otherwise request will fail.
//
// Deprecated: Use PrmObjectPutInit.CopiesNumber instead.
func (x *PrmObjectPutInit) SetCopiesNumberByVectors(copiesNumbers []uint32) {
	x.CopiesNumber = copiesNumbers
}

// SetGRPCPayloadChunkLen sets maximum chunk length value for gRPC Put request.
// Maximum chunk length restricts maximum byte length of the chunk
// transmitted in a single stream message. It depends on
// server settings and other message fields.
// If not specified or negative value set, default value of 3MiB will be used.
//
// Deprecated: Use PrmObjectPutInit.MaxChunkLength instead.
func (x *PrmObjectPutInit) SetGRPCPayloadChunkLen(v int) {
	x.MaxChunkLength = v
}

// ResObjectPut groups the final result values of ObjectPutInit operation.
type ResObjectPut struct {
	statusRes

	obj oid.ID
}

// StoredObjectID returns identifier of the saved object.
func (x ResObjectPut) StoredObjectID() oid.ID {
	return x.obj
}

// ObjectWriter is designed to write one object or
// multiple parts of one object to FrostFS system.
//
// Must be initialized using Client.ObjectPutInit, any other
// usage is unsafe.
type ObjectWriter interface {
	// WriteHeader writes header of the object. Result means success.
	// Failure reason can be received via Close.
	WriteHeader(context.Context, object.Object) bool
	// WritePayloadChunk writes chunk of the object payload. Result means success.
	// Failure reason can be received via Close.
	WritePayloadChunk(context.Context, []byte) bool
	// Close ends writing the object and returns the result of the operation
	// along with the final results. Must be called after using the ObjectWriter.
	//
	// Exactly one return value is non-nil. By default, server status is returned in res structure.
	// Any client's internal or transport errors are returned as Go built-in error.
	// If Client is tuned to resolve FrostFS API statuses, then FrostFS failures
	// codes are returned as error.
	//
	// Return statuses:
	//   - global (see Client docs);
	//   - *apistatus.ContainerNotFound;
	//   - *apistatus.ObjectAccessDenied;
	//   - *apistatus.ObjectLocked;
	//   - *apistatus.LockNonRegularObject;
	//   - *apistatus.SessionTokenNotFound;
	//   - *apistatus.SessionTokenExpired.
	Close(context.Context) (*ResObjectPut, error)
}

// UseKey specifies private key to sign the requests.
// If key is not provided, then Client default key is used.
//
// Deprecated: Use PrmObjectPutInit.Key instead.
func (x *PrmObjectPutInit) UseKey(key ecdsa.PrivateKey) {
	x.Key = &key
}

// WithBearerToken attaches bearer token to be used for the operation.
// Should be called once before any writing steps.
//
// Deprecated: Use PrmObjectPutInit.BearerToken instead.
func (x *PrmObjectPutInit) WithBearerToken(t bearer.Token) {
	x.BearerToken = &t
}

// WithinSession specifies session within which object should be stored.
// Should be called once before any writing steps.
//
// Deprecated: Use PrmObjectPutInit.Session instead.
func (x *PrmObjectPutInit) WithinSession(t session.Object) {
	x.Session = &t
}

// MarkLocal tells the server to execute the operation locally.
//
// Deprecated: Use PrmObjectPutInit.Local instead.
func (x *PrmObjectPutInit) MarkLocal() {
	x.Local = true
}

// WithXHeaders specifies list of extended headers (string key-value pairs)
// to be attached to the request. Must have an even length.
//
// Slice must not be mutated until the operation completes.
//
// Deprecated: Use PrmObjectPutInit.XHeaders instead.
func (x *PrmObjectPutInit) WithXHeaders(hs ...string) {
	x.XHeaders = hs
}

// WithObjectMaxSize specifies max object size value and use it during object splitting.
// When specified, start writing to the stream only after the object is formed.
// Continue processing the input only when the previous formed object has been successfully written.
//
// Deprecated: Use PrmObjectPutInit.MaxSize instead.
func (x *PrmObjectPutInit) WithObjectMaxSize(maxSize uint64) {
	x.MaxSize = maxSize
}

// WithoutHomomorphicHash if set to true do not use Tillich-ZĂ©mor hash for payload.
//
// Deprecated: Use PrmObjectPutInit.WithoutHomomorphHash instead.
func (x *PrmObjectPutInit) WithoutHomomorphicHash(v bool) {
	x.WithoutHomomorphHash = v
}

// WithEpochSource specifies epoch for object when split it on client side.
//
// Deprecated: Use PrmObjectPutInit.EpochSource instead.
func (x *PrmObjectPutInit) WithEpochSource(es transformer.EpochSource) {
	x.EpochSource = es
}

// ObjectPutInit initiates writing an object through a remote server using FrostFS API protocol.
//
// The call only opens the transmission channel, explicit recording is done using the ObjectWriter.
// Exactly one return value is non-nil. Resulting writer must be finally closed.
//
// Returns an error if parameters are set incorrectly.
// Context is required and must not be nil. It is used for network communication.
func (c *Client) ObjectPutInit(ctx context.Context, prm PrmObjectPutInit) (ObjectWriter, error) {
	if prm.MaxSize > 0 {
		return c.objectPutInitTransformer(prm)
	}
	return c.objectPutInitRaw(ctx, prm)
}