package libprometheus

import (
	"fmt"
	"path/filepath"
	"time"

	prometheusreg "github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/config"
	"github.com/prometheus/prometheus/discovery"
	"github.com/prometheus/prometheus/discovery/targetgroup"
	"github.com/prometheus/prometheus/scrape"
	"github.com/prometheus/prometheus/storage"
)

// LoadPrometheusConfig reads and parses dataDir/config.yaml using the
// standard Prometheus config loader.
func LoadPrometheusConfig(dataDir string) (*config.Config, error) {
	path := filepath.Join(dataDir, "config.yaml")
	cfg, err := config.LoadFile(path, false, NewZerologSlogger())
	if err != nil {
		return nil, fmt.Errorf("load prometheus config %q: %w", path, err)
	}
	return cfg, nil
}

// NewScrapeManagerFromConfig creates a scrape.Manager from a loaded Prometheus
// config. It also returns a pre-populated target-sets channel ready to pass to
// Manager.Run() — targets are extracted from the config's static_configs so the
// file remains the single source of truth.
func NewScrapeManagerFromConfig(
	cfg *config.Config,
	appendable storage.Appendable,
	reg prometheusreg.Registerer,
) (*scrape.Manager, chan map[string][]*targetgroup.Group, error) {
	// DiscoveryReloadInterval defaults to 5s in Prometheus v0.310.0+.
	// For a single static target this is just the delay before the first scrape;
	// cap it at 1s so startup latency stays acceptable regardless of the
	// configured scrape interval.
	reloadInterval := time.Duration(cfg.GlobalConfig.ScrapeInterval)
	if reloadInterval == 0 || reloadInterval > time.Second {
		reloadInterval = time.Second
	}
	opts := &scrape.Options{
		DiscoveryReloadInterval: model.Duration(reloadInterval),
	}

	mgr, err := scrape.NewManager(opts, NewZerologSlogger(), nil, appendable, nil, reg)
	if err != nil {
		return nil, nil, fmt.Errorf("create scrape manager: %w", err)
	}

	if err := mgr.ApplyConfig(cfg); err != nil {
		return nil, nil, fmt.Errorf("apply prometheus config: %w", err)
	}

	serviceDiscoveryConfigs := make(map[string]discovery.Configs, len(cfg.ScrapeConfigs))
	for _, sc := range cfg.ScrapeConfigs {
		serviceDiscoveryConfigs[sc.JobName] = sc.ServiceDiscoveryConfigs
	}

	return mgr, newStaticTargetSetsChannel(serviceDiscoveryConfigs), nil
}

// newStaticTargetSetsChannel builds the initial target-set payload for manager
// startup from static discovery configs only.
func newStaticTargetSetsChannel(serviceDiscoveryConfigs map[string]discovery.Configs) chan map[string][]*targetgroup.Group {
	tsets := make(map[string][]*targetgroup.Group, len(serviceDiscoveryConfigs))
	for key, configs := range serviceDiscoveryConfigs {
		tsets[key] = staticTargetGroups(configs)
	}

	ch := make(chan map[string][]*targetgroup.Group, 1)
	ch <- tsets
	return ch
}

func staticTargetGroups(serviceDiscoveryConfigs discovery.Configs) []*targetgroup.Group {
	var groups []*targetgroup.Group
	for _, sdCfg := range serviceDiscoveryConfigs {
		if static, ok := sdCfg.(discovery.StaticConfig); ok {
			groups = append(groups, static...)
		}
	}

	return groups
}
