package api

import (
	"context"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"net/http"
	"testing"

	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
	apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
	bearertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer/test"
	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
	"github.com/nspcc-dev/neo-go/pkg/util"
	"github.com/stretchr/testify/require"
)

const (
	FrostfsNamespaceHeader  = "X-Frostfs-Namespace"
	FrostfsVHSHeader        = "X-Frostfs-S3-VHS"
	FrostfsServernameHeader = "X-Frostfs-Servername"
)

type poolStatisticMock struct {
}

func (p *poolStatisticMock) Statistic() pool.Statistic {
	return pool.Statistic{}
}

type centerMock struct {
	t            *testing.T
	anon         bool
	noAuthHeader bool
	err          error
	attrs        []object.Attribute
	key          *keys.PrivateKey
}

func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) {
	if c.noAuthHeader {
		return nil, middleware.ErrNoAuthorizationHeader
	}

	if c.err != nil {
		return nil, c.err
	}

	var token *bearer.Token

	if !c.anon {
		bt := bearertest.Token()
		token = &bt
		key := c.key
		if key == nil {
			var err error
			key, err = keys.NewPrivateKey()
			require.NoError(c.t, err)
		}
		require.NoError(c.t, token.Sign(key.PrivateKey))
	}

	return &middleware.Box{
		AuthHeaders: &middleware.AuthHeader{},
		AccessBox: &accessbox.Box{
			Gate: &accessbox.GateData{
				BearerToken: token,
			},
		},
		Attributes: c.attrs,
	}, nil
}

type middlewareSettingsMock struct {
	denyByDefault        bool
	sourceIPHeader       string
	domains              []string
	vhsEnabled           bool
	vhsNamespacesEnabled map[string]bool
	logHTTP              middleware.LogHTTPConfig
}

func (r *middlewareSettingsMock) SourceIPHeader() string {
	return r.sourceIPHeader
}

func (r *middlewareSettingsMock) NamespaceHeader() string {
	return FrostfsNamespaceHeader
}

func (r *middlewareSettingsMock) ResolveNamespaceAlias(ns string) string {
	return ns
}

func (r *middlewareSettingsMock) PolicyDenyByDefault() bool {
	return r.denyByDefault
}

func (r *middlewareSettingsMock) Domains() []string {
	return r.domains
}

func (r *middlewareSettingsMock) GlobalVHS() bool {
	return r.vhsEnabled
}

func (r *middlewareSettingsMock) VHSHeader() string {
	return FrostfsVHSHeader
}

func (r *middlewareSettingsMock) ServernameHeader() string {
	return FrostfsServernameHeader
}

func (r *middlewareSettingsMock) VHSNamespacesEnabled() map[string]bool {
	return r.vhsNamespacesEnabled
}
func (r *middlewareSettingsMock) LogHTTPConfig() middleware.LogHTTPConfig {
	return r.logHTTP
}

type frostFSIDMock struct {
	tags            map[string]string
	validateError   bool
	userGroupsError bool
}

func (f *frostFSIDMock) ValidatePublicKey(*keys.PublicKey) error {
	if f.validateError {
		return fmt.Errorf("some error")
	}

	return nil
}

func (f *frostFSIDMock) GetUserGroupIDsAndClaims(util.Uint160) ([]string, map[string]string, error) {
	if f.userGroupsError {
		return nil, nil, fmt.Errorf("some error")
	}
	return []string{}, f.tags, nil
}

type xmlMock struct {
}

func (m *xmlMock) NewXMLDecoder(r io.Reader) *xml.Decoder {
	return xml.NewDecoder(r)
}

type resourceTaggingMock struct {
	bucketTags map[string]string
	objectTags map[string]string
	err        error
}

func (m *resourceTaggingMock) GetBucketTagging(context.Context, *data.BucketInfo) (map[string]string, error) {
	if m.err != nil {
		return nil, m.err
	}
	return m.bucketTags, nil
}

func (m *resourceTaggingMock) GetObjectTagging(context.Context, *data.GetObjectTaggingParams) (string, map[string]string, error) {
	if m.err != nil {
		return "", nil, m.err
	}
	return "", m.objectTags, nil
}

type handlerMock struct {
	t       *testing.T
	cfg     *middlewareSettingsMock
	buckets map[string]*data.BucketInfo
}

type handlerResult struct {
	Method  string
	ReqInfo *middleware.ReqInfo
}

func (h *handlerMock) HeadObjectHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetObjectACLHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) PutObjectACLHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetObjectTaggingHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) PutObjectTaggingHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) DeleteObjectTaggingHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) SelectObjectContentHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetObjectRetentionHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetObjectLegalHoldHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
	res := &handlerResult{
		Method:  middleware.GetObjectOperation,
		ReqInfo: middleware.GetReqInfo(r.Context()),
	}

	h.writeResponse(w, res)
}

func (h *handlerMock) GetObjectAttributesHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) CopyObjectHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) PutObjectRetentionHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) PutObjectLegalHoldHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
	res := &handlerResult{
		Method:  middleware.PutObjectOperation,
		ReqInfo: middleware.GetReqInfo(r.Context()),
	}

	h.writeResponse(w, res)
}

func (h *handlerMock) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
	res := &handlerResult{
		Method:  middleware.DeleteObjectOperation,
		ReqInfo: middleware.GetReqInfo(r.Context()),
	}

	h.writeResponse(w, res)
}

func (h *handlerMock) GetBucketLocationHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetBucketPolicyStatusHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetBucketPolicyHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetBucketLifecycleHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetBucketEncryptionHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetBucketACLHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) PutBucketACLHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetBucketCorsHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) PutBucketCorsHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) DeleteBucketCorsHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetBucketWebsiteHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetBucketAccelerateHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetBucketRequestPaymentHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetBucketLoggingHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetBucketReplicationHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetBucketTaggingHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) DeleteBucketWebsiteHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) DeleteBucketTaggingHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetBucketObjectLockConfigHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetBucketVersioningHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) GetBucketNotificationHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) ListenBucketNotificationHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) ListObjectsV2MHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) {
	res := &handlerResult{
		Method:  "ListObjectsV2",
		ReqInfo: middleware.GetReqInfo(r.Context()),
	}

	h.writeResponse(w, res)
}

func (h *handlerMock) ListBucketObjectVersionsHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) {
	res := &handlerResult{
		Method:  "ListObjectsV1",
		ReqInfo: middleware.GetReqInfo(r.Context()),
	}

	h.writeResponse(w, res)
}

func (h *handlerMock) PutBucketLifecycleHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) PutBucketEncryptionHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) PutBucketPolicyHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) PutBucketObjectLockConfigHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) PutBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
	res := &handlerResult{
		Method:  middleware.PutBucketTaggingOperation,
		ReqInfo: middleware.GetReqInfo(r.Context()),
	}

	h.writeResponse(w, res)
}

func (h *handlerMock) PutBucketVersioningHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) PutBucketNotificationHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) CreateBucketHandler(w http.ResponseWriter, r *http.Request) {
	reqInfo := middleware.GetReqInfo(r.Context())

	h.buckets[reqInfo.Namespace+reqInfo.BucketName] = &data.BucketInfo{
		Name: reqInfo.BucketName,
	}

	res := &handlerResult{
		Method:  middleware.CreateBucketOperation,
		ReqInfo: middleware.GetReqInfo(r.Context()),
	}

	h.writeResponse(w, res)
}

func (h *handlerMock) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
	res := &handlerResult{
		Method:  middleware.HeadBucketOperation,
		ReqInfo: middleware.GetReqInfo(r.Context()),
	}

	h.writeResponse(w, res)
}

func (h *handlerMock) PostObject(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) DeleteMultipleObjectsHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) DeleteBucketPolicyHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) DeleteBucketLifecycleHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) DeleteBucketEncryptionHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) DeleteBucketHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
	res := &handlerResult{
		Method:  middleware.ListBucketsOperation,
		ReqInfo: middleware.GetReqInfo(r.Context()),
	}

	h.writeResponse(w, res)
}

func (h *handlerMock) Preflight(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) AppendCORSHeaders(http.ResponseWriter, *http.Request) {
}

func (h *handlerMock) CreateMultipartUploadHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) UploadPartHandler(w http.ResponseWriter, r *http.Request) {
	res := &handlerResult{
		Method:  "UploadPart",
		ReqInfo: middleware.GetReqInfo(r.Context()),
	}

	h.writeResponse(w, res)
}

func (h *handlerMock) UploadPartCopy(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) CompleteMultipartUploadHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) AbortMultipartUploadHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) ListPartsHandler(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

func (h *handlerMock) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) {
	res := &handlerResult{
		Method:  "ListMultipartUploads",
		ReqInfo: middleware.GetReqInfo(r.Context()),
	}

	h.writeResponse(w, res)
}

func (h *handlerMock) PatchObjectHandler(http.ResponseWriter, *http.Request) {
	panic("implement me")
}

func (h *handlerMock) ResolveBucket(ctx context.Context, name string) (*data.BucketInfo, error) {
	reqInfo := middleware.GetReqInfo(ctx)
	bktInfo, ok := h.buckets[reqInfo.Namespace+name]
	if !ok {
		return nil, apierr.GetAPIError(apierr.ErrNoSuchBucket)
	}
	return bktInfo, nil
}

func (h *handlerMock) ResolveCID(ctx context.Context, bucket string) (cid.ID, error) {
	bktInfo, err := h.ResolveBucket(ctx, bucket)
	if err != nil {
		return cid.ID{}, err
	}
	return bktInfo.CID, nil
}

func (h *handlerMock) writeResponse(w http.ResponseWriter, resp *handlerResult) {
	respData, err := json.Marshal(resp)
	require.NoError(h.t, err)

	_, err = w.Write(respData)
	require.NoError(h.t, err)
}