package frostfs

import (
	"context"
	"errors"
	"fmt"
	"io"

	"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data"
	"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tokens"
	"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tree"
	"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
	apitree "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/tree"
	treepool "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree"
)

type GetNodeByPathResponseInfoWrapper struct {
	response *apitree.GetNodeByPathResponseInfo
}

func (n GetNodeByPathResponseInfoWrapper) GetNodeID() []uint64 {
	return []uint64{n.response.GetNodeID()}
}

func (n GetNodeByPathResponseInfoWrapper) GetParentID() []uint64 {
	return []uint64{n.response.GetParentID()}
}

func (n GetNodeByPathResponseInfoWrapper) GetTimestamp() []uint64 {
	return []uint64{n.response.GetTimestamp()}
}

func (n GetNodeByPathResponseInfoWrapper) GetMeta() []tree.Meta {
	res := make([]tree.Meta, len(n.response.GetMeta()))
	for i, value := range n.response.GetMeta() {
		res[i] = value
	}
	return res
}

type PoolWrapper struct {
	p *treepool.Pool
}

func NewPoolWrapper(p *treepool.Pool) *PoolWrapper {
	return &PoolWrapper{p: p}
}

func (w *PoolWrapper) GetNodes(ctx context.Context, prm *tree.GetNodesParams) ([]tree.NodeResponse, error) {
	ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.GetNodes")
	defer span.End()

	poolPrm := treepool.GetNodesParams{
		CID:           prm.CnrID,
		TreeID:        prm.TreeID,
		Path:          prm.Path,
		Meta:          prm.Meta,
		PathAttribute: tree.FileNameKey,
		LatestOnly:    prm.LatestOnly,
		AllAttrs:      prm.AllAttrs,
		BearerToken:   getBearer(ctx),
	}

	nodes, err := w.p.GetNodes(ctx, poolPrm)
	if err != nil {
		return nil, handleTreeError(err)
	}

	res := make([]tree.NodeResponse, len(nodes))
	for i, info := range nodes {
		res[i] = GetNodeByPathResponseInfoWrapper{info}
	}

	return res, nil
}

func getBearer(ctx context.Context) []byte {
	token, err := tokens.LoadBearerToken(ctx)
	if err != nil {
		return nil
	}
	return token.Marshal()
}

func handleTreeError(err error) error {
	if err == nil {
		return nil
	}
	if errors.Is(err, treepool.ErrNodeNotFound) {
		return fmt.Errorf("%w: %s", tree.ErrNodeNotFound, err.Error())
	}
	if errors.Is(err, treepool.ErrNodeAccessDenied) {
		return fmt.Errorf("%w: %s", tree.ErrNodeAccessDenied, err.Error())
	}

	return err
}

func (w *PoolWrapper) GetSubTree(ctx context.Context, bktInfo *data.BucketInfo, treeID string, rootID []uint64, depth uint32, sort bool) ([]tree.NodeResponse, error) {
	ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.GetSubTree")
	defer span.End()

	order := treepool.NoneOrder
	if sort {
		order = treepool.AscendingOrder
	}
	poolPrm := treepool.GetSubTreeParams{
		CID:         bktInfo.CID,
		TreeID:      treeID,
		RootID:      rootID,
		Depth:       depth,
		BearerToken: getBearer(ctx),
		Order:       order,
	}
	if len(rootID) == 1 && rootID[0] == 0 {
		// storage node interprets 'nil' value as []uint64{0}
		// gate wants to send 'nil' value instead of []uint64{0}, because
		// it provides compatibility with previous tree service api where
		// single uint64(0) value is dropped from signature
		poolPrm.RootID = nil
	}

	subTreeReader, err := w.p.GetSubTree(ctx, poolPrm)
	if err != nil {
		return nil, handleTreeError(err)
	}

	var subtree []tree.NodeResponse

	node, err := subTreeReader.Next()
	for err == nil {
		subtree = append(subtree, GetSubTreeResponseBodyWrapper{node})
		node, err = subTreeReader.Next()
	}
	if err != io.EOF {
		return nil, handleTreeError(err)
	}

	return subtree, nil
}

type GetSubTreeResponseBodyWrapper struct {
	response *apitree.GetSubTreeResponseBody
}

func (n GetSubTreeResponseBodyWrapper) GetNodeID() []uint64 {
	return n.response.GetNodeID()
}

func (n GetSubTreeResponseBodyWrapper) GetParentID() []uint64 {
	resp := n.response.GetParentID()
	if resp == nil {
		// storage sends nil that should be interpreted as []uint64{0}
		// due to protobuf compatibility, see 'GetSubTree' function
		return []uint64{0}
	}
	return resp
}

func (n GetSubTreeResponseBodyWrapper) GetTimestamp() []uint64 {
	return n.response.GetTimestamp()
}

func (n GetSubTreeResponseBodyWrapper) GetMeta() []tree.Meta {
	res := make([]tree.Meta, len(n.response.GetMeta()))
	for i, value := range n.response.GetMeta() {
		res[i] = value
	}
	return res
}