package manager

import (
	"context"
	"sync"

	log "github.com/sirupsen/logrus"
	"go.opentelemetry.io/otel/metric"

	"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/proxy"
	nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
	"github.com/netbirdio/netbird/shared/management/proto"
)

// GRPCController is a concrete implementation that manages proxy clusters and sends updates directly via gRPC.
type GRPCController struct {
	proxyGRPCServer *nbgrpc.ProxyServiceServer
	// Map of cluster address -> set of proxy IDs
	clusterProxies sync.Map
	metrics        *metrics
}

// NewGRPCController creates a new GRPCController.
func NewGRPCController(proxyGRPCServer *nbgrpc.ProxyServiceServer, meter metric.Meter) (*GRPCController, error) {
	m, err := newMetrics(meter)
	if err != nil {
		return nil, err
	}

	return &GRPCController{
		proxyGRPCServer: proxyGRPCServer,
		metrics:         m,
	}, nil
}

// SendServiceUpdateToCluster sends a service update to a specific proxy cluster.
func (c *GRPCController) SendServiceUpdateToCluster(ctx context.Context, accountID string, update *proto.ProxyMapping, clusterAddr string) {
	c.proxyGRPCServer.SendServiceUpdateToCluster(ctx, update, clusterAddr)
	c.metrics.IncrementServiceUpdateSendCount()
}

// GetOIDCValidationConfig returns the OIDC validation configuration from the gRPC server.
func (c *GRPCController) GetOIDCValidationConfig() proxy.OIDCValidationConfig {
	return c.proxyGRPCServer.GetOIDCValidationConfig()
}

// RegisterProxyToCluster registers a proxy to a specific cluster for routing.
func (c *GRPCController) RegisterProxyToCluster(ctx context.Context, clusterAddr, proxyID string) error {
	if clusterAddr == "" {
		return nil
	}
	proxySet, _ := c.clusterProxies.LoadOrStore(clusterAddr, &sync.Map{})
	proxySet.(*sync.Map).Store(proxyID, struct{}{})
	log.WithContext(ctx).Debugf("Registered proxy %s to cluster %s", proxyID, clusterAddr)

	c.metrics.IncrementProxyConnectionCount()

	return nil
}

// UnregisterProxyFromCluster removes a proxy from a cluster.
func (c *GRPCController) UnregisterProxyFromCluster(ctx context.Context, clusterAddr, proxyID string) error {
	if clusterAddr == "" {
		return nil
	}
	if proxySet, ok := c.clusterProxies.Load(clusterAddr); ok {
		proxySet.(*sync.Map).Delete(proxyID)
		log.WithContext(ctx).Debugf("Unregistered proxy %s from cluster %s", proxyID, clusterAddr)

		c.metrics.DecrementProxyConnectionCount()
	}
	return nil
}

// GetProxiesForCluster returns all proxy IDs registered for a specific cluster.
func (c *GRPCController) GetProxiesForCluster(clusterAddr string) []string {
	proxySet, ok := c.clusterProxies.Load(clusterAddr)
	if !ok {
		return nil
	}

	var proxies []string
	proxySet.(*sync.Map).Range(func(key, _ interface{}) bool {
		proxies = append(proxies, key.(string))
		return true
	})
	return proxies
}
