// Package runtimeconfig keeps the active database-backed integration settings
// and notifies long-lived provider clients when they change.
package runtimeconfig

import (
	"encoding/json"
	"strings"
	"sync"
)

type Config struct {
	Kind     string
	Provider string
	Enabled  bool
	Values   json.RawMessage
}

type listener struct {
	kind     string
	provider string
	callback func(Config)
}

type Store struct {
	mu        sync.RWMutex
	values    map[string]Config
	listeners map[uint64]listener
	nextID    uint64
}

func NewStore() *Store {
	return &Store{values: make(map[string]Config), listeners: make(map[uint64]listener)}
}

func configKey(kind, provider string) string {
	return strings.ToLower(strings.TrimSpace(kind)) + "/" + strings.ToLower(strings.TrimSpace(provider))
}

func cloneConfig(config Config) Config {
	config.Values = append(json.RawMessage(nil), config.Values...)
	return config
}

func (s *Store) Get(kind, provider string) (Config, bool) {
	if s == nil {
		return Config{}, false
	}
	s.mu.RLock()
	config, ok := s.values[configKey(kind, provider)]
	s.mu.RUnlock()
	return cloneConfig(config), ok
}

func (s *Store) Set(config Config) {
	if s == nil {
		return
	}
	config.Kind = strings.ToLower(strings.TrimSpace(config.Kind))
	config.Provider = strings.ToLower(strings.TrimSpace(config.Provider))
	config = cloneConfig(config)
	key := configKey(config.Kind, config.Provider)
	s.mu.Lock()
	s.values[key] = config
	callbacks := s.matchingListenersLocked(config.Kind, config.Provider)
	s.mu.Unlock()
	for _, callback := range callbacks {
		callback(cloneConfig(config))
	}
}

func (s *Store) Delete(kind, provider string) {
	if s == nil {
		return
	}
	kind = strings.ToLower(strings.TrimSpace(kind))
	provider = strings.ToLower(strings.TrimSpace(provider))
	s.mu.Lock()
	delete(s.values, configKey(kind, provider))
	callbacks := s.matchingListenersLocked(kind, provider)
	s.mu.Unlock()
	config := Config{Kind: kind, Provider: provider}
	for _, callback := range callbacks {
		callback(config)
	}
}

func (s *Store) Replace(configs []Config) {
	if s == nil {
		return
	}
	next := make(map[string]Config, len(configs))
	for _, config := range configs {
		config.Kind = strings.ToLower(strings.TrimSpace(config.Kind))
		config.Provider = strings.ToLower(strings.TrimSpace(config.Provider))
		config = cloneConfig(config)
		next[configKey(config.Kind, config.Provider)] = config
	}
	s.mu.Lock()
	previous := s.values
	s.values = next
	listeners := make([]listener, 0, len(s.listeners))
	for _, item := range s.listeners {
		listeners = append(listeners, item)
	}
	s.mu.Unlock()

	changed := make(map[string]Config, len(previous)+len(next))
	for key, config := range previous {
		changed[key] = Config{Kind: config.Kind, Provider: config.Provider}
	}
	for key, config := range next {
		changed[key] = config
	}
	for _, item := range listeners {
		if config, ok := changed[configKey(item.kind, item.provider)]; ok {
			item.callback(cloneConfig(config))
		}
	}
}

func (s *Store) Subscribe(kind, provider string, callback func(Config)) func() {
	if s == nil || callback == nil {
		return func() {}
	}
	s.mu.Lock()
	s.nextID++
	id := s.nextID
	s.listeners[id] = listener{kind: strings.ToLower(strings.TrimSpace(kind)), provider: strings.ToLower(strings.TrimSpace(provider)), callback: callback}
	s.mu.Unlock()
	return func() {
		s.mu.Lock()
		delete(s.listeners, id)
		s.mu.Unlock()
	}
}

func (s *Store) matchingListenersLocked(kind, provider string) []func(Config) {
	callbacks := make([]func(Config), 0)
	for _, item := range s.listeners {
		if item.kind == kind && item.provider == provider {
			callbacks = append(callbacks, item.callback)
		}
	}
	return callbacks
}
