package talos

import (
	"fmt"
	"log/slog"

	"github.com/budimanjojo/talhelper/v3/pkg/config"
	"github.com/budimanjojo/talhelper/v3/pkg/patcher"
	tconfig "github.com/siderolabs/talos/pkg/machinery/config"
	"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
)

// AddMultiDocs appends multi-documents machine configuration to the main machine configuration
// generated by upstream API. The documents are generated and applied as a strategic merge patch.
// It also returns an error, if any.
func AddMultiDocs(node *config.Node, mode string, corecfg []byte, vc *tconfig.VersionContract) ([]byte, error) {
	extradocs, err := genMultiDocs(node, mode, vc)
	if err != nil {
		return nil, err
	}
	if extradocs == nil {
		return corecfg, nil
	}

	// TODO: A better way is to append them as documents and use upstream API
	// But the problem is upstream Generate API is generating not just the core config
	// Resulting in error because of duplicates with the ones we created
	return patcher.YamlBytesPatcher(extradocs, corecfg)
}

// appendNetworkConfig is a helper function that checks if multidoc network config is supported
// and if network interfaces exist, then generates and appends the config bytes to result.
func appendNetworkConfig(
	result *[]byte,
	vc *tconfig.VersionContract,
	node *config.Node,
	configType string,
	generator func([]*v1alpha1.Device) ([]byte, error),
) error {
	if !vc.MultidocNetworkConfigSupported() || len(node.NetworkInterfaces) == 0 {
		return nil
	}

	slog.Debug(fmt.Sprintf("generating %s config for %s", configType, node.Hostname))
	cfg, err := generator(node.NetworkInterfaces)
	if err != nil {
		return err
	}
	if cfg != nil {
		*result = append(*result, cfg...)
	}
	return nil
}

func genMultiDocs(node *config.Node, mode string, vc *tconfig.VersionContract) ([]byte, error) {
	var result []byte

	if vc.MultidocNetworkConfigSupported() && (len(node.Nameservers) > 0 || node.DisableSearchDomain) {
		slog.Debug(fmt.Sprintf("generating resolver config for %s", node.Hostname))
		rc, err := GenerateResolverConfigBytes(node.Nameservers, node.DisableSearchDomain)
		if err != nil {
			return nil, err
		}
		result = append(result, rc...)
	}

	if vc.MultidocNetworkConfigSupported() {
		slog.Debug(fmt.Sprintf("generating hostname config for %s", node.Hostname))
		hc, err := GenerateNetworkHostnameConfigBytes(node.Hostname, node.IgnoreHostname)
		if err != nil {
			return nil, err
		}

		result = append(result, hc...)
	}

	if node.IngressFirewall != nil {
		slog.Debug(fmt.Sprintf("generating machine firewall config for %s", node.Hostname))
		nc, err := GenerateNetworkConfigBytes(node.IngressFirewall)
		if err != nil {
			return nil, err
		}
		result = append(result, nc...)
	}

	if len(node.ExtensionServices) > 0 {
		slog.Debug(fmt.Sprintf("generating machine extension service config for %s", node.Hostname))
		ext, err := GenerateExtensionServicesConfigBytes(node.ExtensionServices)
		if err != nil {
			return nil, err
		}
		result = append(result, ext...)
	}

	if len(node.Volumes) > 0 {
		slog.Debug(fmt.Sprintf("generating volume config for %s", node.Hostname))
		vc, err := GenerateVolumeConfigBytes(node.Volumes, mode)
		if err != nil {
			return nil, err
		}
		result = append(result, vc...)
	}

	if len(node.UserVolumes) > 0 {
		slog.Debug(fmt.Sprintf("generating user volume config for %s", node.Hostname))
		uvc, err := GenerateUserVolumeConfigBytes(node.UserVolumes, mode)
		if err != nil {
			return nil, err
		}
		result = append(result, uvc...)
	}

	if err := appendNetworkConfig(&result, vc, node, "link alias", GenerateLinkAliasConfigBytes); err != nil {
		return nil, err
	}

	if err := appendNetworkConfig(&result, vc, node, "bond member alias", GenerateBondMemberAliasConfigBytes); err != nil {
		return nil, err
	}

	if err := appendNetworkConfig(&result, vc, node, "bond", GenerateBondConfigBytes); err != nil {
		return nil, err
	}

	if err := appendNetworkConfig(&result, vc, node, "dhcp", GenerateDHCPConfigsBytes); err != nil {
		return nil, err
	}

	if err := appendNetworkConfig(&result, vc, node, "vip", GenerateVIPConfigBytes); err != nil {
		return nil, err
	}

	if err := appendNetworkConfig(&result, vc, node, "link", GenerateLinkConfigBytes); err != nil {
		return nil, err
	}

	if err := appendNetworkConfig(&result, vc, node, "vlan", GenerateVLANConfigBytes); err != nil {
		return nil, err
	}

	if err := appendNetworkConfig(&result, vc, node, "wireguard", GenerateWireguardConfigBytes); err != nil {
		return nil, err
	}

	if err := appendNetworkConfig(&result, vc, node, "bridge", GenerateBridgeConfigBytes); err != nil {
		return nil, err
	}

	return result, nil
}
