package dnsresolver

import (
	"bytes"
	"html/template"
	"net"
	"path/filepath"
	"strings"

	bosherr "github.com/cloudfoundry/bosh-utils/errors"
	boshsys "github.com/cloudfoundry/bosh-utils/system"
)

const (
	resolvConfTemplate = `# Generated by bosh-agent
{{ range .DNSServers }}nameserver {{ . }}
{{ end }}`
)

type resolveConfResolver struct {
	fs        boshsys.FileSystem
	cmdRunner boshsys.CmdRunner
}

func NewResolveConfResolver(
	fs boshsys.FileSystem,
	cmdRunner boshsys.CmdRunner,
) DNSResolver {
	return &resolveConfResolver{
		fs:        fs,
		cmdRunner: cmdRunner,
	}
}

func (d *resolveConfResolver) Validate(dnsServers []string) error {
	if len(dnsServers) == 0 {
		return nil
	}

	resolvConfContents, err := d.fs.ReadFileString("/etc/resolv.conf")
	if err != nil {
		return bosherr.WrapError(err, "Reading /etc/resolv.conf")
	}

	for _, dnsServer := range dnsServers {
		if strings.Contains(resolvConfContents, dnsServer) {
			return nil
		}

		canonicalIP := net.ParseIP(dnsServer)

		if canonicalIP != nil {
			if strings.Contains(resolvConfContents, canonicalIP.String()) {
				return nil
			}
		}
	}

	return bosherr.WrapError(err, "None of the DNS servers that were specified in the manifest were found in /etc/resolv.conf.")
}

func (d *resolveConfResolver) SetupDNS(dnsServers []string) error {
	buffer := bytes.NewBuffer([]byte{})

	t := template.Must(template.New("resolv-conf").Parse(resolvConfTemplate))

	type dnsConfigArg struct {
		DNSServers []string
	}

	dnsServersArg := dnsConfigArg{dnsServers}

	err := t.Execute(buffer, dnsServersArg)
	if err != nil {
		return bosherr.WrapError(err, "Generating DNS config from template")
	}

	if len(dnsServers) > 0 { //nolint:nestif
		// Write out base so that releases may overwrite head
		err = d.fs.WriteFile("/etc/resolvconf/resolv.conf.d/base", buffer.Bytes())
		if err != nil {
			return bosherr.WrapError(err, "Writing to /etc/resolvconf/resolv.conf.d/base")
		}
	} else {
		// For the first time before resolv.conf is symlinked to /run/...
		// inherit possibly configured resolv.conf

		targetPath, err := d.fs.ReadAndFollowLink("/etc/resolv.conf")
		if err != nil {
			return bosherr.WrapError(err, "Reading /etc/resolv.conf symlink")
		}

		expectedPath, err := filepath.Abs("/etc/resolv.conf")
		if err != nil {
			return bosherr.WrapError(err, "Resolving path to native OS")
		}
		if targetPath == expectedPath {
			err := d.fs.CopyFile("/etc/resolv.conf", "/etc/resolvconf/resolv.conf.d/base")
			if err != nil {
				return bosherr.WrapError(err, "Copying /etc/resolv.conf for backwards compat")
			}
		}
	}

	err = d.fs.Symlink("/run/resolvconf/resolv.conf", "/etc/resolv.conf")
	if err != nil {
		return bosherr.WrapError(err, "Setting up /etc/resolv.conf symlink")
	}

	_, _, _, err = d.cmdRunner.RunCommand("resolvconf", "-u")
	if err != nil {
		return bosherr.WrapError(err, "Updating resolvconf")
	}

	return nil
}
