package getsvc

import (
	"context"
	"errors"

	objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
	clientcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/client"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
	objectSvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object"
	getsvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/get"
	objutil "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/util"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
	objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
	"go.uber.org/zap"
)

// Service implements Get operation of Object service v2.
type Service struct {
	*cfg
}

// Option represents Service constructor option.
type Option func(*cfg)

type clientSource interface {
	Get(info clientcore.NodeInfo) (clientcore.MultiAddressClient, error)
}

type cfg struct {
	svc *getsvc.Service

	keyStorage *objutil.KeyStorage

	clientSource clientSource

	netmapSource netmap.Source

	announcedKeys netmap.AnnouncedKeys

	contSource container.Source

	log *logger.Logger
}

// NewService constructs Service instance from provided options.
func NewService(svc *getsvc.Service,
	keyStorage *objutil.KeyStorage,
	clientSource clientSource,
	netmapSource netmap.Source,
	announcedKeys netmap.AnnouncedKeys,
	contSource container.Source,
	opts ...Option,
) *Service {
	c := &cfg{
		svc:           svc,
		keyStorage:    keyStorage,
		clientSource:  clientSource,
		netmapSource:  netmapSource,
		announcedKeys: announcedKeys,
		contSource:    contSource,
		log:           &logger.Logger{Logger: zap.L()},
	}

	for i := range opts {
		opts[i](c)
	}

	return &Service{
		cfg: c,
	}
}

// Get calls internal service and returns v2 object stream.
func (s *Service) Get(req *objectV2.GetRequest, stream objectSvc.GetObjectStream) error {
	p, err := s.toPrm(req, stream)
	if err != nil {
		return err
	}

	err = s.svc.Get(stream.Context(), *p)

	var splitErr *objectSDK.SplitInfoError
	var ecErr *objectSDK.ECInfoError

	switch {
	case errors.As(err, &splitErr):
		return stream.Send(splitInfoResponse(splitErr.SplitInfo()))
	case errors.As(err, &ecErr):
		return stream.Send(ecInfoResponse(ecErr.ECInfo()))
	default:
		return err
	}
}

// GetRange calls internal service and returns v2 payload range stream.
func (s *Service) GetRange(req *objectV2.GetRangeRequest, stream objectSvc.GetObjectRangeStream) error {
	p, err := s.toRangePrm(req, stream)
	if err != nil {
		return err
	}

	err = s.svc.GetRange(stream.Context(), *p)

	var splitErr *objectSDK.SplitInfoError
	var ecErr *objectSDK.ECInfoError

	switch {
	case errors.As(err, &splitErr):
		return stream.Send(splitInfoRangeResponse(splitErr.SplitInfo()))
	case errors.As(err, &ecErr):
		return stream.Send(ecInfoRangeResponse(ecErr.ECInfo()))
	default:
		return err
	}
}

// Head serves ForstFS API v2 compatible HEAD requests.
func (s *Service) Head(ctx context.Context, req *objectV2.HeadRequest) (*objectV2.HeadResponse, error) {
	resp := new(objectV2.HeadResponse)
	resp.SetBody(new(objectV2.HeadResponseBody))

	p, err := s.toHeadPrm(req, resp)
	if err != nil {
		return nil, err
	}

	err = s.svc.Head(ctx, *p)

	var splitErr *objectSDK.SplitInfoError
	var ecErr *objectSDK.ECInfoError

	if errors.As(err, &splitErr) {
		setSplitInfoHeadResponse(splitErr.SplitInfo(), resp)
		err = nil
	}
	if errors.As(err, &ecErr) {
		setECInfoHeadResponse(ecErr.ECInfo(), resp)
		err = nil
	}

	return resp, err
}

func WithLogger(l *logger.Logger) Option {
	return func(c *cfg) {
		c.log = &logger.Logger{Logger: l.With(zap.String("component", "Object.Get V2 service"))}
	}
}