package client

import (
	"errors"
	"fmt"

	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
	"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"
	apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
)

// structure is embedded to all resulting types in order to inherit status-related methods.
type statusRes struct {
	st apistatus.Status
}

// Status returns server's status return.
//
// Use apistatus package functionality to handle the status.
func (x statusRes) Status() apistatus.Status {
	return x.st
}

func writeXHeadersToMeta(xHeaders []string, h *v2session.RequestMetaHeader) {
	if len(xHeaders) == 0 {
		return
	}

	// TODO (aarifullin): remove the panic when all client parameters will check XHeaders
	// within buildRequest invocation.
	if len(xHeaders)%2 != 0 {
		panic("slice of X-Headers with odd length")
	}

	hs := make([]v2session.XHeader, len(xHeaders)/2)
	for i := 0; i < len(xHeaders); i += 2 {
		hs[i].SetKey(xHeaders[i])
		hs[i].SetValue(xHeaders[i+1])
	}

	h.SetXHeaders(hs)
}

// error messages.
var (
	errorMissingContainer = errors.New("missing container")
	errorMissingObject    = errors.New("missing object")
	errorAccountNotSet    = errors.New("account not set")
	errorServerAddrUnset  = errors.New("server address is unset or empty")
	errorZeroRangeLength  = errors.New("zero range length")
	errorMissingRanges    = errors.New("missing ranges")
	errorInvalidXHeaders  = errors.New("xheaders must be presented only as key-value pairs")
)

type request interface {
	GetMetaHeader() *v2session.RequestMetaHeader
	SetMetaHeader(*v2session.RequestMetaHeader)
	SetVerificationHeader(*v2session.RequestVerificationHeader)
}

func (c *Client) prepareRequest(req request, meta *v2session.RequestMetaHeader) {
	ttl := meta.GetTTL()
	if ttl == 0 {
		ttl = 2
	}

	verV2 := meta.GetVersion()
	if verV2 == nil {
		verV2 = new(refs.Version)
		version.Current().WriteToV2(verV2)
	}

	meta.SetTTL(ttl)
	meta.SetVersion(verV2)
	meta.SetNetworkMagic(c.prm.NetMagic)

	req.SetMetaHeader(meta)
}

// processResponse verifies response signature and converts status to an error if needed.
func (c *Client) processResponse(resp responseV2) (apistatus.Status, error) {
	if c.prm.ResponseInfoCallback != nil {
		rmi := ResponseMetaInfo{
			key:   resp.GetVerificationHeader().GetBodySignature().GetKey(),
			epoch: resp.GetMetaHeader().GetEpoch(),
		}
		if err := c.prm.ResponseInfoCallback(rmi); err != nil {
			return nil, fmt.Errorf("response callback error: %w", err)
		}
	}

	err := signature.VerifyServiceMessage(resp)
	if err != nil {
		return nil, fmt.Errorf("invalid response signature: %w", err)
	}

	st := apistatus.FromStatusV2(resp.GetMetaHeader().GetStatus())
	if !c.prm.DisableFrostFSErrorResolution {
		return st, apistatus.ErrFromStatus(st)
	}
	return st, nil
}

// ExecRaw executes f with underlying git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client.Client
// instance. Communicate over the Protocol Buffers protocol in a more flexible way:
// most often used to transmit data over a fixed version of the FrostFS protocol, as well
// as to support custom services.
//
// The f must not manipulate the client connection passed into it.
//
// Like all other operations, must be called after connecting to the server and
// before closing the connection.
//
// See also Dial and Close.
// See also git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client package docs.
func (c *Client) ExecRaw(f func(client *client.Client) error) error {
	return f(&c.c)
}