package regional_proxy

import (
	"context"
	"errors"
	"time"

	"braces.dev/errtrace"
	"github.com/allegro/bigcache"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/encoding/protojson"

	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	regionalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/regional/serverless/v1"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/pkg/utils"
)

var tagBufPool utils.ByteBufferPool

type service struct {
	cache *bigcache.BigCache
}

func NewService() globalserverlessv1.RegionalServerProxyServiceServer {
	cfg := bigcache.DefaultConfig(time.Minute * 15)
	cfg.OnRemoveWithReason = func(key string, entry []byte, reason bigcache.RemoveReason) {
		tilog.PlainWarn("remove id from cache", zap.String("key", key), zap.Any("reason", reason))
	}
	cfg.HardMaxCacheSize = 512

	cache, err := bigcache.NewBigCache(cfg)
	if err != nil {
		panic(err)
	}

	return &service{
		cache: cache,
	}
}

func (c *service) GetDataByIds(
	ctx context.Context,
	req *globalserverlessv1.RegionalServerProxyServiceGetDataByIdsRequest,
) (*globalserverlessv1.RegionalServerProxyServiceGetDataByIdsResponse, error) {
	out := make([]*regionalserverlessv1.ServerlessRegionalServiceClusterInfo, 0)
	for _, id := range req.Ids {
		data, err := c.cache.Get(id)
		if err != nil {
			if !errors.Is(err, bigcache.ErrEntryNotFound) {
				tilog.Error(ctx, "get cluster from cache failed", zap.String("id", id), zap.Error(err))
			}
			return &globalserverlessv1.RegionalServerProxyServiceGetDataByIdsResponse{
				Exist: false,
			}, nil
		}

		var info regionalserverlessv1.ServerlessRegionalServiceClusterInfo
		if err := protojson.Unmarshal(data, &info); err != nil {
			tilog.Error(ctx, "unmarshal failed", zap.String("id", id), zap.Error(err))
			return nil, errtrace.Wrap(err)
		}

		out = append(out, &info)
	}

	return &globalserverlessv1.RegionalServerProxyServiceGetDataByIdsResponse{
		Exist:    true,
		Clusters: out,
	}, nil
}

func marshal(cluster *regionalserverlessv1.ServerlessRegionalServiceClusterInfo) ([]byte, error) {
	bb := tagBufPool.Get()
	defer tagBufPool.Put(bb)

	return errtrace.Wrap2(protojson.MarshalOptions{}.MarshalAppend(bb.B[:0], cluster))
}

func (c *service) Delete(
	ctx context.Context,
	req *globalserverlessv1.RegionalServerProxyServiceDeleteRequest,
) (*globalserverlessv1.RegionalServerProxyServiceDeleteResponse, error) {
	err := c.cache.Delete(req.Id)
	if err != nil && !errors.Is(err, bigcache.ErrEntryNotFound) {
		tilog.Error(ctx, "delete id from cache failed", zap.String("id", req.Id), zap.Error(err))
		return nil, errtrace.Wrap(err)
	}
	return &globalserverlessv1.RegionalServerProxyServiceDeleteResponse{
		Ok: true,
	}, nil
}

func (c *service) Set(
	ctx context.Context,
	req *globalserverlessv1.RegionalServerProxyServiceSetRequest,
) (*globalserverlessv1.RegionalServerProxyServiceSetResponse, error) {
	if err := c.setData(ctx, req.Id, req.Cluster); err != nil {
		tilog.Error(ctx, "set data failed", zap.String("id", req.Id), zap.Error(err))
		return nil, errtrace.Wrap(err)
	}

	return &globalserverlessv1.RegionalServerProxyServiceSetResponse{
		Ok: true,
	}, nil
}

func (c *service) setData(
	ctx context.Context,
	id string,
	cluster *regionalserverlessv1.ServerlessRegionalServiceClusterInfo,
) error {
	data, err := marshal(cluster)
	if err != nil {
		return errtrace.Wrap(err)
	}

	if err := c.cache.Set(id, data); err != nil {
		tilog.Error(ctx, "set data failed", zap.String("id", id), zap.Error(err))
		return errtrace.Wrap(err)
	}

	return nil
}

func (c *service) BatchSet(
	ctx context.Context,
	req *globalserverlessv1.RegionalServerProxyServiceBatchSetRequest,
) (*globalserverlessv1.RegionalServerProxyServiceBatchSetResponse, error) {
	for _, cluster := range req.Clusters {
		if err := c.setData(ctx, cluster.ClusterId, cluster); err != nil {
			tilog.Error(ctx, "set data failed", zap.String("id", cluster.ClusterId), zap.Error(err))
			return nil, errtrace.Wrap(err)
		}
	}

	return &globalserverlessv1.RegionalServerProxyServiceBatchSetResponse{
		Ok: true,
	}, nil
}

func (c *service) DeleteAll(
	context.Context,
	*globalserverlessv1.RegionalServerProxyServiceDeleteAllRequest,
) (*globalserverlessv1.RegionalServerProxyServiceDeleteAllResponse, error) {
	err := c.cache.Reset()
	if err != nil {
		return nil, errtrace.Wrap(err)
	}

	return &globalserverlessv1.RegionalServerProxyServiceDeleteAllResponse{
		Ok: true,
	}, nil
}
