package client

import (
	"context"
	"fmt"

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

// PrmBalanceGet groups parameters of BalanceGet operation.
type PrmBalanceGet struct {
	XHeaders []string

	Account *user.ID
}

// SetAccount sets identifier of the FrostFS account for which the balance is requested.
// Required parameter.
//
// Deprecated: Use PrmBalanceGet.Account instead.
func (x *PrmBalanceGet) SetAccount(id user.ID) {
	x.Account = &id
}

func (x *PrmBalanceGet) buildRequest(c *Client) (*v2accounting.BalanceRequest, error) {
	if x.Account == nil {
		return nil, errorAccountNotSet
	}

	var accountV2 refs.OwnerID
	x.Account.WriteToV2(&accountV2)

	var body v2accounting.BalanceRequestBody
	body.SetOwnerID(&accountV2)

	var req v2accounting.BalanceRequest
	req.SetBody(&body)

	c.prepareRequest(&req, new(v2session.RequestMetaHeader))
	return &req, nil
}

// ResBalanceGet groups resulting values of BalanceGet operation.
type ResBalanceGet struct {
	statusRes

	amount accounting.Decimal
}

// Amount returns current amount of funds on the FrostFS account as decimal number.
func (x ResBalanceGet) Amount() accounting.Decimal {
	return x.amount
}

// BalanceGet requests current balance of the FrostFS account.
//
// 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 `error`,
// If PrmInit.DisableFrostFSFailuresResolution has been called, unsuccessful
// FrostFS status codes are included in the returned result structure,
// otherwise, are also returned as `error`.
//
// Returns an error if parameters are set incorrectly (see PrmBalanceGet docs).
// Context is required and must not be nil. It is used for network communication.
//
// Return statuses:
//   - global (see Client docs).
func (c *Client) BalanceGet(ctx context.Context, prm PrmBalanceGet) (*ResBalanceGet, error) {
	req, err := prm.buildRequest(c)
	if err != nil {
		return nil, err
	}

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

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

	var res ResBalanceGet
	res.st, err = c.processResponse(resp)
	if err != nil || !apistatus.IsSuccessful(res.st) {
		return &res, err
	}

	const fieldBalance = "balance"

	bal := resp.GetBody().GetBalance()
	if bal == nil {
		return &res, newErrMissingResponseField(fieldBalance)
	}

	if err := res.amount.ReadFromV2(*bal); err != nil {
		return &res, newErrInvalidResponseField(fieldBalance, err)
	}
	return &res, nil
}