package ddwrt

import (
	"bytes"
	"fmt"
	"os/exec"
	"strings"
	"text/template"

	"github.com/nextdns/nextdns/config"
	"github.com/nextdns/nextdns/router/internal"
)

type Router struct {
	ListenPort      string
	ClientReporting bool
	CacheEnabled    bool
	savedParams     []string
}

func New() (*Router, bool) {
	if b, err := exec.Command("uname", "-o").Output(); err != nil ||
		!strings.HasPrefix(string(b), "DD-WRT") {
		return nil, false
	}
	return &Router{
		ListenPort: "5342",
	}, true
}

func (r *Router) String() string {
	return "ddwrt"
}

func (r *Router) Configure(c *config.Config) error {
	c.Listens = []string{"127.0.0.1:" + r.ListenPort}
	r.ClientReporting = c.ReportClientInfo
	if cs, _ := config.ParseBytes(c.CacheSize); cs > 0 {
		r.CacheEnabled = true
		c.Listens = []string{":53"}
		return r.setupDNSMasq() // Make dnsmasq stop listening on 53 before we do.
	}
	return nil
}

func (r *Router) Setup() error {
	if !r.CacheEnabled {
		return r.setupDNSMasq()
	}
	return nil
}

func (r *Router) setupDNSMasq() error {
	t, err := template.New("").Parse(tmpl)
	if err != nil {
		return err
	}

	var buf bytes.Buffer
	if err = t.Execute(&buf, r); err != nil {
		return err
	}

	// Save nvram values so we can restore them.
	if r.savedParams, err = internal.NVRAM(
		"dns_dnsmasq",
		"dnsmasq_options",
		"dns_crypt",
		"dnssec",
		"dnsmasq_no_dns_rebind",
		"dnsmasq_add_mac"); err != nil {
		return err
	}

	// Configure the firmware:
	// * Add dnsmasq options to route queries to nextdns
	// * DNS rebinding is disabled, as DNS blocking uses 0.0.0.0 to block domains.
	//   The rebinding protection can be setup and enforced at NextDNS level.
	// * DNSCrypt is disabled as it would conflict.
	// * DNSSEC validation is disabled as when a DNSSEC supported domain is blocked,
	//   the validation will fail as blocking alters the response. NextDNS takes care
	//   of DNS validation for non blocked queries.
	// * DNS over TLS is disabled so stubby does not run for nothing.
	if err := internal.SetNVRAM(
		"dns_dnsmasq=1",
		"dnsmasq_options="+buf.String(),
		"dns_crypt=0",
		"dnssec=0",
		"dnsmasq_no_dns_rebind=0",
		"dnsmasq_add_mac=0"); err != nil {
		return err
	}

	// Restart dnsmasq service to apply changes.
	return restartDNSMasq()
}

func (r *Router) Restore() error {
	// Restore previous settings.
	if err := internal.SetNVRAM(r.savedParams...); err != nil {
		return err
	}
	// Restart dnsmasq service to apply changes.
	return restartDNSMasq()
}

func restartDNSMasq() error {
	if err := exec.Command("stopservice", "dnsmasq").Run(); err != nil {
		return fmt.Errorf("stopservice dnsmasq: %v", err)
	}
	if err := exec.Command("startservice", "dnsmasq").Run(); err != nil {
		return fmt.Errorf("startservice dnsmasq: %v", err)
	}
	return nil
}

var tmpl = `# Configuration generated by NextDNS
{{- if .CacheEnabled}}
# DNS is handled by NextDNS
port=0
{{- else}}
no-resolv
server=127.0.0.1#{{.ListenPort}}
{{- if .ClientReporting}}
add-mac
{{- end}}
add-subnet=32,128
{{- end}}
`
