package app

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"

	"github.com/canonical/k8s/pkg/log"
	"github.com/canonical/k8s/pkg/utils"
	"github.com/canonical/microcluster/v2/state"
)

const systemConfFileHeader = "# AUTOGENERATED - DO NOT EDIT\n# Generated by the k8s snap for system tuning.\n# Delete this file to disable auto-tuning.\n# Overrides defaults via 'sysctl --system' on k8sd service start.\n"

var (
	reK8sConfFile = regexp.MustCompile(`^(\d+)-k8s.conf$`)
	reConfFiles   = regexp.MustCompile(`^(\d+)-.*\.conf$`)
)

// ensureSystemTuningConfigFile checks for a k8s system configuration tuning
// and creates one at the highest order if the file does not exist yet.
func (a *App) ensureSystemTuningConfigFile(ctx context.Context) error {
	log := log.FromContext(ctx).WithValues("hook", "bootstrap")

	confPath, err := utils.GetFileMatches(a.snap.SystemTuningConfigDir(), reK8sConfFile)
	if err != nil {
		log.Error(err, "failed to check for existing system tuning file")
		return err
	}

	if len(confPath) != 0 {
		return nil
	}

	maxOrder, err := GetHighestConfigFileOrder(a.snap.SystemTuningConfigDir(), reConfFiles)
	if err != nil {
		log.Error(err, "failed to check for highest order system tuning file")
		return err
	}

	// f, err := os.Create(filepath.Join(a.snap.SystemTuningConfigDir(), fmt.Sprintf("%d-k8s.conf", maxOrder+1)))
	newFile := filepath.Join(a.snap.SystemTuningConfigDir(), fmt.Sprintf("%d-k8s.conf", maxOrder+1))
	data := []byte(systemConfFileHeader)

	if err = utils.WriteFile(newFile, data, 0o600); err != nil {
		log.Error(err, "failed to create system tuning file")
		return err
	}
	return nil
}

// tuneSystemSettings bumps system parameters if the configuration file exists.
// This ensures system settings reflect minimum k8s requirements.
func (a *App) tuneSystemSettings(ctx context.Context, s state.State) error {
	log := log.FromContext(ctx).WithValues("startup", "tuneSystem")

	confPath, err := utils.GetFileMatches(a.snap.SystemTuningConfigDir(), reK8sConfFile)
	if err != nil {
		log.Error(err, "failed to get file match on system config file *-k8s.conf")
	}

	// skip tuning if the tuning file does not exist
	if len(confPath) == 0 {
		log.Info(fmt.Sprintf("skipping system tuning: no sysctl config file under %s", a.snap.SystemTuningConfigDir()))
		return nil
	}

	// parse config files to check minimum requirements are set
	confFile := filepath.Join(a.snap.SystemTuningConfigDir(), confPath[0])
	newConfig := utils.MinConfigFileDiff(a.snap.SystemConfigDirs(), a.snap.SystemMinConfig(), confFile)
	if len(newConfig) == 0 {
		return nil
	}
	// Update the x-k8s.conf file to ensure minimum k8s requirements
	if err := utils.SerializeArgumentFile(newConfig, confFile, systemConfFileHeader); err != nil {
		log.Error(err, fmt.Sprintf("failed to update system configuration file %s", confPath[0]))
	}

	if err := exec.CommandContext(ctx, "sysctl", "--system").Run(); err != nil {
		return fmt.Errorf("failed to apply system parameters: %w", err)
	}
	return nil
}

// GetHighestConfigFileOrder returns the path of the file in a dir matching a file like 10-xyz.conf
// using the regex `^(\d+)-.*\.conf$` or returns 0s if no match was found.
func GetHighestConfigFileOrder(path string, reConfFiles *regexp.Regexp) (int, error) {
	maxOrder := 0
	entries, err := os.ReadDir(path)
	if err != nil {
		return maxOrder, err
	}

	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}
		matches := reConfFiles.FindStringSubmatch(entry.Name())
		if matches == nil {
			continue
		}

		// Check for configuration file order number
		numStr := matches[1]
		num, err := strconv.Atoi(numStr)
		if err != nil {
			continue
		}
		if num > maxOrder {
			maxOrder = num
		}
	}

	return maxOrder, nil
}
