package raid

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"
	"time"

	"imuslab.com/bokofs/bokofsd/mod/disktool/diskfs"
	"imuslab.com/bokofs/bokofsd/mod/utils"
)

/*
	mdadmConf.go

	This package handles the config modification and update for
	the mdadm module


*/

// Force mdadm to stop all RAID and load fresh from config file
// on some Linux distro this is required as mdadm start too early
func (m *Manager) FlushReload() error {
	//Get a list of currently running RAID devices
	raidDevices, err := m.GetRAIDDevicesFromProcMDStat()
	if err != nil {
		return err
	}

	//Stop all of the running RAID devices
	for _, rd := range raidDevices {

		//Check if it is mounted. If yes, skip this
		devMounted, err := diskfs.DeviceIsMounted("/dev/" + rd.Name)
		if devMounted || err != nil {
			log.Println("[RAID] " + "/dev/" + rd.Name + " is in use. Skipping.")
			continue
		}
		log.Println("[RAID] Stopping " + rd.Name)

		cmdMdadm := exec.Command("sudo", "mdadm", "--stop", "/dev/"+rd.Name)

		// Run the command and capture its output
		_, err = cmdMdadm.Output()
		if err != nil {
			log.Println("[RAID] Unable to stop " + rd.Name + ". Skipping")
			continue
		}
	}

	time.Sleep(300 * time.Millisecond)

	//Assemble mdadm array again
	err = m.RestartRAIDService()
	if err != nil {
		return err
	}

	return nil
}

// removeDevicesEntry remove device hardcode from mdadm config file
func removeDevicesEntry(configLine string) string {
	// Split the config line by space character
	tokens := strings.Fields(configLine)

	// Iterate through the tokens to find and remove the devices=* part
	for i, token := range tokens {
		if strings.HasPrefix(token, "devices=") {
			// Remove the devices=* part from the slice
			tokens = append(tokens[:i], tokens[i+1:]...)
			break
		}
	}

	// Join the tokens back into a single string
	updatedConfigLine := strings.Join(tokens, " ")

	return updatedConfigLine
}

// Updates the mdadm configuration file with the details of RAID arrays
// so the RAID drive will still be seen after a reboot (hopefully)
// this will automatically add / remove config base on current runtime setup
func (m *Manager) UpdateMDADMConfig() error {
	cmdMdadm := exec.Command("sudo", "mdadm", "--detail", "--scan", "--verbose")

	// Run the command and capture its output
	output, err := cmdMdadm.Output()
	if err != nil {
		return fmt.Errorf("error running mdadm command: %v", err)
	}

	//Load the config from system
	currentConfigBytes, err := os.ReadFile("/etc/mdadm/mdadm.conf")
	if err != nil {
		return fmt.Errorf("unable to open mdadm.conf: " + err.Error())
	}
	currentConf := string(currentConfigBytes)

	//Check if the current config already contains the setting
	newConfigLines := []string{}
	uuidsInNewConfig := []string{}
	arrayConfigs := strings.TrimSpace(string(output))
	lines := strings.Split(arrayConfigs, "ARRAY")
	for _, line := range lines {
		//For each line, you should have something like this
		//ARRAY /dev/md0 metadata=1.2 name=debian:0 UUID=cbc11a2b:fbd42653:99c1340b:9c4962fb
		//   devices=/dev/sdb,/dev/sdc
		//Building structure for RAID Config Record

		line = strings.ReplaceAll(line, "\n", " ")
		fields := strings.Fields(line)
		if len(fields) < 5 {
			continue
		}
		poolUUID := strings.TrimPrefix(fields[3], "UUID=")
		uuidsInNewConfig = append(uuidsInNewConfig, poolUUID)
		//Check if this uuid already in the config file
		if strings.Contains(currentConf, poolUUID) {
			continue
		}

		//This config not exists in the settings. Add it to append lines
		log.Println("[RAID] Adding " + fields[0] + " (UUID=" + poolUUID + ") into mdadm config")
		settingLine := "ARRAY " + strings.Join(fields, " ")

		//Remove the device specific names
		settingLine = removeDevicesEntry(settingLine)
		newConfigLines = append(newConfigLines, settingLine)
	}

	originalConfigLines := strings.Split(strings.TrimSpace(currentConf), "\n")
	poolUUIDToBeRemoved := []string{}
	for _, line := range originalConfigLines {
		lineFields := strings.Fields(line)
		for _, thisField := range lineFields {
			if strings.HasPrefix(thisField, "UUID=") {
				//This is the UUID of this array. Check if it still exists in new storage config
				thisPoolUUID := strings.TrimPrefix(thisField, "UUID=")
				existsInNewConfig := utils.StringInArray(uuidsInNewConfig, thisPoolUUID)
				if !existsInNewConfig {
					//Label this UUID to be removed
					poolUUIDToBeRemoved = append(poolUUIDToBeRemoved, thisPoolUUID)
				}

				//Skip scanning the remaining fields of this RAID pool
				break
			}
		}
	}

	if len(poolUUIDToBeRemoved) > 0 {
		//Remove the old UUIDs from config
		for _, volumeUUID := range poolUUIDToBeRemoved {
			err = m.RemoveVolumeFromMDADMConfig(volumeUUID)
			if err != nil {
				log.Println("[RAID] Error when trying to remove old RAID volume from config: " + err.Error())
				return err
			} else {
				log.Println("[RAID] RAID volume " + volumeUUID + " removed from config file")
			}
		}

	}

	if len(newConfigLines) == 0 {
		//Nothing to write
		log.Println("[RAID] Nothing to write. Skipping mdadm config update.")
		return nil
	}

	// Construct the bash command to append the line to mdadm.conf using echo and tee
	for _, configLine := range newConfigLines {
		cmd := exec.Command("bash", "-c", fmt.Sprintf(`echo "%s" | sudo tee -a /etc/mdadm/mdadm.conf`, configLine))

		// Run the command
		err := cmd.Run()
		if err != nil {
			return fmt.Errorf("error injecting line into mdadm.conf: %v", err)
		}
	}

	return nil
}

// Removes a RAID volume from the mdadm configuration file given its volume UUID.
// Note that this only remove a single line of config. If your line consists of multiple lines
// you might need to remove it manually
func (m *Manager) RemoveVolumeFromMDADMConfig(volumeUUID string) error {
	// Construct the sed command to remove the line containing the volume UUID from mdadm.conf
	sedCommand := fmt.Sprintf(`sudo sed -i '/UUID=%s/d' /etc/mdadm/mdadm.conf`, volumeUUID)

	// Execute the sed command
	cmd := exec.Command("bash", "-c", sedCommand)
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("error removing volume from mdadm.conf: %v", err)
	}

	return nil
}
