package capabilities

import (
	"context"
	"errors"
	"sync"

	"github.com/smartcontractkit/chainlink-common/pkg/capabilities"
	"github.com/smartcontractkit/chainlink-common/pkg/capabilities/registry"
	"github.com/smartcontractkit/chainlink-common/pkg/logger"
	"github.com/smartcontractkit/chainlink-common/pkg/types/core"

	p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types"
)

// Registry is a struct for the registry of capabilities.
// Registry is safe for concurrent use.
type Registry struct {
	core.UnimplementedCapabilitiesRegistryMetadata
	core.CapabilitiesRegistryBase

	metadataRegistry core.CapabilitiesRegistryMetadata
	lggr             logger.Logger
	mu               sync.RWMutex
}

func (r *Registry) LocalNode(ctx context.Context) (capabilities.Node, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	if r.metadataRegistry == nil {
		return capabilities.Node{}, errors.New("metadataRegistry information not available")
	}

	return r.metadataRegistry.LocalNode(ctx)
}

func (r *Registry) NodeByPeerID(ctx context.Context, peerID p2ptypes.PeerID) (capabilities.Node, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	if r.metadataRegistry == nil {
		return capabilities.Node{}, errors.New("metadataRegistry information not available")
	}
	return r.metadataRegistry.NodeByPeerID(ctx, peerID)
}

func (r *Registry) ConfigForCapability(ctx context.Context, capabilityID string, donID uint32) (capabilities.CapabilityConfiguration, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	if r.metadataRegistry == nil {
		return capabilities.CapabilityConfiguration{}, errors.New("metadataRegistry information not available")
	}

	return r.metadataRegistry.ConfigForCapability(ctx, capabilityID, donID)
}

func (r *Registry) DONsForCapability(ctx context.Context, capabilityID string) ([]capabilities.DONWithNodes, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	if r.metadataRegistry == nil {
		return nil, errors.New("metadataRegistry information not available")
	}

	return r.metadataRegistry.DONsForCapability(ctx, capabilityID)
}

// SetLocalRegistry sets a local copy of the offchain registry for the registry to use.
// This is only public for testing purposes; the only production use should be from the CapabilitiesLauncher.
func (r *Registry) SetLocalRegistry(lr core.CapabilitiesRegistryMetadata) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.metadataRegistry = lr
}

// NewRegistry returns a new Registry.
func NewRegistry(lggr logger.Logger) *Registry {
	return &Registry{
		CapabilitiesRegistryBase: registry.NewBaseRegistry(lggr),
		lggr:                     logger.Named(lggr, "CapabilitiesRegistry"),
	}
}

// TestMetadataRegistry is a test implementation of the metadataRegistry
// interface. It is used when ExternalCapabilitiesRegistry is not available.
type TestMetadataRegistry struct {
	core.UnimplementedCapabilitiesRegistryMetadata
}

func (t *TestMetadataRegistry) LocalNode(ctx context.Context) (capabilities.Node, error) {
	peerID := p2ptypes.PeerID{}
	workflowDON := capabilities.DON{
		ID:            1,
		ConfigVersion: 1,
		Members: []p2ptypes.PeerID{
			peerID,
		},
		F:                0,
		IsPublic:         false,
		AcceptsWorkflows: true,
	}
	return capabilities.Node{
		PeerID:         &peerID,
		WorkflowDON:    workflowDON,
		CapabilityDONs: []capabilities.DON{},
	}, nil
}

func (t *TestMetadataRegistry) NodeByPeerID(ctx context.Context, _ p2ptypes.PeerID) (capabilities.Node, error) {
	return t.LocalNode(ctx)
}

func (t *TestMetadataRegistry) ConfigForCapability(ctx context.Context, capabilityID string, donID uint32) (capabilities.CapabilityConfiguration, error) {
	return capabilities.CapabilityConfiguration{}, nil
}

func (t *TestMetadataRegistry) DONsForCapability(ctx context.Context, capabilityID string) ([]capabilities.DONWithNodes, error) {
	return []capabilities.DONWithNodes{}, nil
}
