package tree

import (
	"context"
	"encoding/hex"
	"fmt"
	"net"
	"strings"

	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/converter"
	aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request"
	core "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
	checkercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/common/ape"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
	cnrSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
	commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
	nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
	"google.golang.org/grpc/peer"
)

func (s *Service) newAPERequest(ctx context.Context, namespace string,
	cid cid.ID, operation acl.Op, role acl.Role, publicKey *keys.PublicKey,
) (aperequest.Request, error) {
	schemaMethod, err := converter.SchemaMethodFromACLOperation(operation)
	if err != nil {
		return aperequest.Request{}, err
	}
	schemaRole, err := converter.SchemaRoleFromACLRole(role)
	if err != nil {
		return aperequest.Request{}, err
	}
	reqProps := map[string]string{
		nativeschema.PropertyKeyActorPublicKey: hex.EncodeToString(publicKey.Bytes()),
		nativeschema.PropertyKeyActorRole:      schemaRole,
	}
	reqProps, err = s.fillWithUserClaimTags(reqProps, publicKey)
	if err != nil {
		return aperequest.Request{}, err
	}
	if p, ok := peer.FromContext(ctx); ok {
		if tcpAddr, ok := p.Addr.(*net.TCPAddr); ok {
			reqProps[commonschema.PropertyKeyFrostFSSourceIP] = tcpAddr.IP.String()
		}
	}

	var resourceName string
	if namespace == "root" || namespace == "" {
		resourceName = fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cid.EncodeToString())
	} else {
		resourceName = fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainerObjects, namespace, cid.EncodeToString())
	}

	return aperequest.NewRequest(
		schemaMethod,
		aperequest.NewResource(resourceName, make(map[string]string)),
		reqProps,
	), nil
}

func (s *Service) checkAPE(ctx context.Context, bt *bearer.Token,
	container *core.Container, cid cid.ID, operation acl.Op, role acl.Role, publicKey *keys.PublicKey,
) error {
	namespace := ""
	cntNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(container.Value).Zone(), ".ns")
	if hasNamespace {
		namespace = cntNamespace
	}

	request, err := s.newAPERequest(ctx, namespace, cid, operation, role, publicKey)
	if err != nil {
		return fmt.Errorf("failed to create ape request: %w", err)
	}

	return s.apeChecker.CheckAPE(checkercore.CheckPrm{
		Request:        request,
		Namespace:      namespace,
		Container:      cid,
		ContainerOwner: container.Value.Owner(),
		PublicKey:      publicKey,
		BearerToken:    bt,
		SoftAPECheck:   false,
	})
}

// fillWithUserClaimTags fills ape request properties with user claim tags getting them from frostfsid contract by actor public key.
func (s *Service) fillWithUserClaimTags(reqProps map[string]string, publicKey *keys.PublicKey) (map[string]string, error) {
	if reqProps == nil {
		reqProps = make(map[string]string)
	}
	props, err := aperequest.FormFrostfsIDRequestProperties(s.frostfsidSubjectProvider, publicKey)
	if err != nil {
		return reqProps, err
	}
	for propertyName, properyValue := range props {
		reqProps[propertyName] = properyValue
	}
	return reqProps, nil
}