// SPDX-License-Identifier: GPL-3.0-or-later

package file

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/confgroup"

	"gopkg.in/yaml.v2"
)

type format int

const (
	unknownFormat format = iota
	unknownEmptyFormat
	staticFormat
	sdFormat
)

func parse(req confgroup.Registry, path string) (*confgroup.Group, error) {
	bs, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	if len(bs) == 0 {
		return nil, nil
	}

	switch cfgFormat(bs) {
	case staticFormat:
		return parseStaticFormat(req, path, bs)
	case sdFormat:
		return parseSDFormat(req, path, bs)
	case unknownEmptyFormat:
		return nil, nil
	default:
		return nil, fmt.Errorf("unknown file format: '%s'", path)
	}
}

func parseStaticFormat(reg confgroup.Registry, path string, bs []byte) (*confgroup.Group, error) {
	name := fileName(path)
	// TODO: properly handle module renaming
	// See agent/setup.go buildDiscoveryConf() for details
	if name == "wmi" {
		name = "windows"
	}
	modDef, ok := reg.Lookup(name)
	if !ok {
		return nil, nil
	}

	var modCfg staticConfig
	if err := yaml.Unmarshal(bs, &modCfg); err != nil {
		return nil, err
	}

	for _, cfg := range modCfg.Jobs {
		cfg.SetModule(name)
		def := mergeDef(modCfg.Default, modDef)
		cfg.ApplyDefaults(def)
	}

	group := &confgroup.Group{
		Configs: modCfg.Jobs,
		Source:  path,
	}

	return group, nil
}

func parseSDFormat(reg confgroup.Registry, path string, bs []byte) (*confgroup.Group, error) {
	var cfgs sdConfig
	if err := yaml.Unmarshal(bs, &cfgs); err != nil {
		return nil, err
	}

	var i int
	for _, cfg := range cfgs {
		if def, ok := reg.Lookup(cfg.Module()); ok && cfg.Module() != "" {
			cfg.ApplyDefaults(def)
			cfgs[i] = cfg
			i++
		}
	}

	group := &confgroup.Group{
		Configs: cfgs[:i],
		Source:  path,
	}

	return group, nil
}

func cfgFormat(bs []byte) format {
	var data any
	if err := yaml.Unmarshal(bs, &data); err != nil {
		return unknownFormat
	}
	if data == nil {
		return unknownEmptyFormat
	}

	type (
		static = map[any]any
		sd     = []any
	)
	switch data.(type) {
	case static:
		return staticFormat
	case sd:
		return sdFormat
	default:
		return unknownFormat
	}
}

func mergeDef(a, b confgroup.Default) confgroup.Default {
	return confgroup.Default{
		MinUpdateEvery:     firstPositive(a.MinUpdateEvery, b.MinUpdateEvery),
		UpdateEvery:        firstPositive(a.UpdateEvery, b.UpdateEvery),
		AutoDetectionRetry: firstPositive(a.AutoDetectionRetry, b.AutoDetectionRetry),
		Priority:           firstPositive(a.Priority, b.Priority),
	}
}

func firstPositive(value int, others ...int) int {
	if value > 0 || len(others) == 0 {
		return value
	}
	return firstPositive(others[0], others[1:]...)
}

func fileName(path string) string {
	_, file := filepath.Split(path)
	ext := filepath.Ext(path)
	return file[:len(file)-len(ext)]
}
