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"
	v2refs "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/bearer"
	apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
)

// PrmObjectDelete groups parameters of ObjectDelete operation.
type PrmObjectDelete struct {
	XHeaders []string

	BearerToken *bearer.Token

	Session *session.Object

	ContainerID *cid.ID

	ObjectID *oid.ID

	Key *ecdsa.PrivateKey
}

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

// ResObjectDelete groups resulting values of ObjectDelete operation.
type ResObjectDelete struct {
	statusRes

	tomb oid.ID
}

// Tombstone returns identifier of the created tombstone object.
func (x ResObjectDelete) Tombstone() oid.ID {
	return x.tomb
}

func (prm *PrmObjectDelete) buildRequest(c *Client) (*v2object.DeleteRequest, error) {
	if prm.ContainerID == nil {
		return nil, errorMissingContainer
	}

	if prm.ObjectID == nil {
		return nil, errorMissingObject
	}

	if len(prm.XHeaders)%2 != 0 {
		return nil, errorInvalidXHeaders
	}

	meta := new(v2session.RequestMetaHeader)
	writeXHeadersToMeta(prm.XHeaders, meta)

	if prm.BearerToken != nil {
		v2BearerToken := new(acl.BearerToken)
		prm.BearerToken.WriteToV2(v2BearerToken)
		meta.SetBearerToken(v2BearerToken)
	}

	if prm.Session != nil {
		v2SessionToken := new(v2session.Token)
		prm.Session.WriteToV2(v2SessionToken)
		meta.SetSessionToken(v2SessionToken)
	}

	addr := new(v2refs.Address)

	cnrV2 := new(v2refs.ContainerID)
	prm.ContainerID.WriteToV2(cnrV2)
	addr.SetContainerID(cnrV2)

	objV2 := new(v2refs.ObjectID)
	prm.ObjectID.WriteToV2(objV2)
	addr.SetObjectID(objV2)

	body := new(v2object.DeleteRequestBody)
	body.SetAddress(addr)

	req := new(v2object.DeleteRequest)
	req.SetBody(body)
	c.prepareRequest(req, meta)

	return req, nil
}

// ObjectDelete marks an object for deletion from the container using FrostFS API protocol.
// As a marker, a special unit called a tombstone is placed in the container.
// It confirms the user's intent to delete the object, and is itself a container object.
// Explicit deletion is done asynchronously, and is generally not guaranteed.
//
// Returns a list of checksums in raw form: the format of hashes and their number
// is left for the caller to check. Client preserves the order of the server's response.
//
// 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.ResolveFrostFSFailures has been called, unsuccessful
// FrostFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
//
// Returns an error if parameters are set incorrectly (see PrmObjectDelete docs).
// Context is required and must not be nil. It is used for network communication.
//
// Return statuses:
//   - global (see Client docs)
//   - *apistatus.ContainerNotFound;
//   - *apistatus.ObjectAccessDenied;
//   - *apistatus.ObjectLocked;
//   - *apistatus.SessionTokenExpired.
func (c *Client) ObjectDelete(ctx context.Context, prm PrmObjectDelete) (*ResObjectDelete, error) {
	req, err := prm.buildRequest(c)
	if err != nil {
		return nil, err
	}

	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.DeleteObject(&c.c, req, client.WithContext(ctx))
	if err != nil {
		return nil, err
	}

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

	if !apistatus.IsSuccessful(res.st) {
		return &res, nil
	}

	const fieldTombstone = "tombstone"

	idTombV2 := resp.GetBody().GetTombstone().GetObjectID()
	if idTombV2 == nil {
		return nil, newErrMissingResponseField(fieldTombstone)
	}

	err = res.tomb.ReadFromV2(*idTombV2)
	if err != nil {
		return nil, newErrInvalidResponseField(fieldTombstone, err)
	}

	return &res, nil
}