package v2

import (
	"crypto/ecdsa"
	"fmt"

	sessionV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
	sessionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
)

// RequestInfo groups parsed version-independent (from SDK library)
// request information and raw API request.
type RequestInfo struct {
	basicACL    acl.Basic
	requestRole acl.Role
	operation   acl.Op  // put, get, head, etc.
	cnrOwner    user.ID // container owner

	// cnrNamespace defined to which namespace a container is belonged.
	cnrNamespace string

	idCnr cid.ID

	// optional for some request
	// e.g. Put, Search
	obj *oid.ID

	senderKey []byte

	bearer *bearer.Token // bearer token of request

	srcRequest any
}

func (r *RequestInfo) SetBasicACL(basicACL acl.Basic) {
	r.basicACL = basicACL
}

func (r *RequestInfo) SetRequestRole(requestRole acl.Role) {
	r.requestRole = requestRole
}

func (r *RequestInfo) SetSenderKey(senderKey []byte) {
	r.senderKey = senderKey
}

// Request returns raw API request.
func (r RequestInfo) Request() any {
	return r.srcRequest
}

// ContainerOwner returns owner if the container.
func (r RequestInfo) ContainerOwner() user.ID {
	return r.cnrOwner
}

func (r RequestInfo) ContainerNamespace() string {
	return r.cnrNamespace
}

// ObjectID return object ID.
func (r RequestInfo) ObjectID() *oid.ID {
	return r.obj
}

// ContainerID return container ID.
func (r RequestInfo) ContainerID() cid.ID {
	return r.idCnr
}

// CleanBearer forces cleaning bearer token information.
func (r *RequestInfo) CleanBearer() {
	r.bearer = nil
}

// Bearer returns bearer token of the request.
func (r RequestInfo) Bearer() *bearer.Token {
	return r.bearer
}

// BasicACL returns basic ACL of the container.
func (r RequestInfo) BasicACL() acl.Basic {
	return r.basicACL
}

// SenderKey returns public key of the request's sender.
func (r RequestInfo) SenderKey() []byte {
	return r.senderKey
}

// Operation returns request's operation.
func (r RequestInfo) Operation() acl.Op {
	return r.operation
}

// RequestRole returns request sender's role.
func (r RequestInfo) RequestRole() acl.Role {
	return r.requestRole
}

// IsSoftAPECheck states if APE should perform soft checks.
// Soft APE check allows a request if CheckAPE returns NoRuleFound for it,
// otherwise it denies the request.
func (r RequestInfo) IsSoftAPECheck() bool {
	return r.BasicACL().Bits() != 0
}

// MetaWithToken groups session and bearer tokens,
// verification header and raw API request.
type MetaWithToken struct {
	vheader *sessionV2.RequestVerificationHeader
	token   *sessionSDK.Object
	bearer  *bearer.Token
	src     any
}

// RequestOwner returns ownerID and its public key
// according to internal meta information.
func (r MetaWithToken) RequestOwner() (*user.ID, *keys.PublicKey, error) {
	if r.vheader == nil {
		return nil, nil, errEmptyVerificationHeader
	}

	if r.bearer != nil && r.bearer.Impersonate() {
		return unmarshalPublicKeyWithOwner(r.bearer.SigningKeyBytes())
	}

	// if session token is presented, use it as truth source
	if r.token != nil {
		// verify signature of session token
		return ownerFromToken(r.token)
	}

	// otherwise get original body signature
	bodySignature := originalBodySignature(r.vheader)
	if bodySignature == nil {
		return nil, nil, errEmptyBodySig
	}

	return unmarshalPublicKeyWithOwner(bodySignature.GetKey())
}

func unmarshalPublicKeyWithOwner(rawKey []byte) (*user.ID, *keys.PublicKey, error) {
	key, err := unmarshalPublicKey(rawKey)
	if err != nil {
		return nil, nil, fmt.Errorf("invalid signature key: %w", err)
	}

	var idSender user.ID
	user.IDFromKey(&idSender, (ecdsa.PublicKey)(*key))

	return &idSender, key, nil
}