package client

import (
	"context"
	"crypto/ecdsa"
	"fmt"

	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
	v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
	rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
	v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
)

// PrmObjectPutSingle groups parameters of PutSingle operation.
type PrmObjectPutSingle struct {
	copyNum []uint32
	meta    v2session.RequestMetaHeader
	object  *v2object.Object
	key     *ecdsa.PrivateKey
}

// SetCopiesNumber 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.
func (x *PrmObjectPutSingle) SetCopiesNumber(v []uint32) {
	x.copyNum = v
}

// UseKey specifies private key to sign the requests.
// If key is not provided, then Client default key is used.
func (x *PrmObjectPutSingle) 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.
func (x *PrmObjectPutSingle) WithBearerToken(t bearer.Token) {
	v2token := &acl.BearerToken{}
	t.WriteToV2(v2token)
	x.meta.SetBearerToken(v2token)
}

// WithinSession specifies session within which object should be stored.
// Should be called once before any writing steps.
func (x *PrmObjectPutSingle) WithinSession(t session.Object) {
	tv2 := &v2session.Token{}
	t.WriteToV2(tv2)
	x.meta.SetSessionToken(tv2)
}

// ExecuteLocal tells the server to execute the operation locally.
func (x *PrmObjectPutSingle) ExecuteLocal() {
	x.meta.SetTTL(1)
}

// 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.
func (x *PrmObjectPutSingle) WithXHeaders(hs ...string) {
	writeXHeadersToMeta(hs, &x.meta)
}

// SetObject specifies prepared object to put.
func (x *PrmObjectPutSingle) SetObject(o *v2object.Object) {
	x.object = o
}

// ResObjectPutSingle groups resulting values of PutSingle operation.
type ResObjectPutSingle struct {
	statusRes
}

// ObjectPutSingle writes prepared object to FrostFS.
// Object must have payload, also containerID, objectID, ownerID, payload hash, payload length of an object must be set.
// 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.
func (c *Client) ObjectPutSingle(ctx context.Context, prm PrmObjectPutSingle) (*ResObjectPutSingle, error) {
	body := &v2object.PutSingleRequestBody{}
	body.SetCopiesNumber(prm.copyNum)
	body.SetObject(prm.object)

	req := &v2object.PutSingleRequest{}
	req.SetBody(body)

	c.prepareRequest(req, &prm.meta)

	key := &c.prm.key
	if prm.key != nil {
		key = prm.key
	}

	err := signature.SignServiceMessage(key, req)
	if err != nil {
		return nil, fmt.Errorf("sign request: %w", err)
	}

	resp, err := rpcapi.PutSingleObject(&c.c, req, client.WithContext(ctx))
	if err != nil {
		return nil, err
	}

	var res ResObjectPutSingle
	res.st, err = c.processResponse(resp)
	if err != nil {
		return nil, err
	}

	return &res, nil
}