package api

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

	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
	apiErrors "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"

type poolStatisticMock struct {
}

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

type centerMock struct {
	t     *testing.T
	anon  bool
	attrs []object.Attribute
}

func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) {
	var token *bearer.Token

	if !c.anon {
		bt := bearertest.Token()
		token = &bt
		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
	aclEnabled     bool
	sourceIPHeader string
}

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) ACLEnabled() bool {
	return r.aclEnabled
}

type frostFSIDMock struct {
	tags map[string]string
}

func (f *frostFSIDMock) ValidatePublicKey(*keys.PublicKey) error {
	return nil
}

func (f *frostFSIDMock) GetUserGroupIDsAndClaims(util.Uint160) ([]string, map[string]string, 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
	noSuchKey  bool
}

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

func (m *resourceTaggingMock) GetObjectTagging(context.Context, *data.GetObjectTaggingParams) (string, map[string]string, error) {
	if m.noSuchKey {
		return "", nil, apiErrors.GetAPIError(apiErrors.ErrNoSuchKey)
	}
	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(http.ResponseWriter, *http.Request) {
	//TODO implement me
	panic("implement me")
}

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,
		APEEnabled: !h.cfg.ACLEnabled(),
	}

	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) ResolveBucket(ctx context.Context, name string) (*data.BucketInfo, error) {
	reqInfo := middleware.GetReqInfo(ctx)
	bktInfo, ok := h.buckets[reqInfo.Namespace+name]
	if !ok {
		return nil, apiErrors.GetAPIError(apiErrors.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)
}