package dns

import (
	"bytes"
	"fmt"
	"os"

	log "github.com/sirupsen/logrus"
)

const (
	fileGeneratedResolvConfContentHeader      = "# Generated by NetBird"
	fileGeneratedResolvConfSearchBeginContent = "search "
	fileGeneratedResolvConfContentFormat      = fileGeneratedResolvConfContentHeader +
		"\n# If needed you can restore the original file by copying back %s\n\nnameserver %s\n" +
		fileGeneratedResolvConfSearchBeginContent + "%s\n"
)

const (
	fileDefaultResolvConfBackupLocation = defaultResolvConfPath + ".original.netbird"
	fileMaxLineCharsLimit               = 256
	fileMaxNumberOfSearchDomains        = 6
)

var fileSearchLineBeginCharCount = len(fileGeneratedResolvConfSearchBeginContent)

type fileConfigurator struct {
	originalPerms os.FileMode
}

func newFileConfigurator() (hostManager, error) {
	return &fileConfigurator{}, nil
}

func (f *fileConfigurator) supportCustomPort() bool {
	return false
}

func (f *fileConfigurator) applyDNSConfig(config hostDNSConfig) error {
	backupFileExist := false
	_, err := os.Stat(fileDefaultResolvConfBackupLocation)
	if err == nil {
		backupFileExist = true
	}

	if !config.routeAll {
		if backupFileExist {
			err = f.restore()
			if err != nil {
				return fmt.Errorf("unable to configure DNS for this peer using file manager without a Primary nameserver group. Restoring the original file return err: %s", err)
			}
		}
		return fmt.Errorf("unable to configure DNS for this peer using file manager without a nameserver group with all domains configured")
	}
	managerType, err := getOSDNSManagerType()
	if err != nil {
		return err
	}
	switch managerType {
	case fileManager, netbirdManager:
		if !backupFileExist {
			err = f.backup()
			if err != nil {
				return fmt.Errorf("unable to backup the resolv.conf file")
			}
		}
	default:
		// todo improve this and maybe restart DNS manager from scratch
		return fmt.Errorf("something happened and file manager is not your prefered host dns configurator, restart the agent")
	}

	var searchDomains string
	appendedDomains := 0
	for _, dConf := range config.domains {
		if dConf.matchOnly || dConf.disabled {
			continue
		}
		if appendedDomains >= fileMaxNumberOfSearchDomains {
			// lets log all skipped domains
			log.Infof("already appended %d domains to search list. Skipping append of %s domain", fileMaxNumberOfSearchDomains, dConf.domain)
			continue
		}
		if fileSearchLineBeginCharCount+len(searchDomains) > fileMaxLineCharsLimit {
			// lets log all skipped domains
			log.Infof("search list line is larger than %d characters. Skipping append of %s domain", fileMaxLineCharsLimit, dConf.domain)
			continue
		}

		searchDomains += " " + dConf.domain
		appendedDomains++
	}
	content := fmt.Sprintf(fileGeneratedResolvConfContentFormat, fileDefaultResolvConfBackupLocation, config.serverIP, searchDomains)
	err = writeDNSConfig(content, defaultResolvConfPath, f.originalPerms)
	if err != nil {
		err = f.restore()
		if err != nil {
			log.Errorf("attempt to restore default file failed with error: %s", err)
		}
		return err
	}
	log.Infof("created a NetBird managed %s file with your DNS settings. Added %d search domains. Search list: %s", defaultResolvConfPath, appendedDomains, searchDomains)
	return nil
}

func (f *fileConfigurator) restoreHostDNS() error {
	return f.restore()
}

func (f *fileConfigurator) backup() error {
	stats, err := os.Stat(defaultResolvConfPath)
	if err != nil {
		return fmt.Errorf("got an error while checking stats for %s file. Error: %s", defaultResolvConfPath, err)
	}

	f.originalPerms = stats.Mode()

	err = copyFile(defaultResolvConfPath, fileDefaultResolvConfBackupLocation)
	if err != nil {
		return fmt.Errorf("got error while backing up the %s file. Error: %s", defaultResolvConfPath, err)
	}
	return nil
}

func (f *fileConfigurator) restore() error {
	err := copyFile(fileDefaultResolvConfBackupLocation, defaultResolvConfPath)
	if err != nil {
		return fmt.Errorf("got error while restoring the %s file from %s. Error: %s", defaultResolvConfPath, fileDefaultResolvConfBackupLocation, err)
	}

	return os.RemoveAll(fileDefaultResolvConfBackupLocation)
}

func writeDNSConfig(content, fileName string, permissions os.FileMode) error {
	log.Debugf("creating managed file %s", fileName)
	var buf bytes.Buffer
	buf.WriteString(content)
	err := os.WriteFile(fileName, buf.Bytes(), permissions)
	if err != nil {
		return fmt.Errorf("got an creating resolver file %s. Error: %s", fileName, err)
	}
	return nil
}

func copyFile(src, dest string) error {
	stats, err := os.Stat(src)
	if err != nil {
		return fmt.Errorf("got an error while checking stats for %s file when copying it. Error: %s", src, err)
	}

	bytesRead, err := os.ReadFile(src)
	if err != nil {
		return fmt.Errorf("got an error while reading the file %s file for copy. Error: %s", src, err)
	}

	err = os.WriteFile(dest, bytesRead, stats.Mode())
	if err != nil {
		return fmt.Errorf("got an writing the destination file %s for copy. Error: %s", dest, err)
	}
	return nil
}
