package merlin

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"strings"

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

type Router struct {
	DNSMasqPath     string
	ListenPort      string
	ClientReporting bool
	CacheEnabled    bool
	CurrentPostConf string
	johnFork        bool
}

func New() (*Router, bool) {
	b, err := exec.Command("uname", "-o").Output()
	if err != nil || !strings.HasPrefix(string(b), "ASUSWRT-Merlin") {
		return nil, false
	}
	postConfPath := "/jffs/scripts/dnsmasq.postconf"
	return &Router{
		DNSMasqPath:     postConfPath,
		CurrentPostConf: readPostConf(postConfPath),
		ListenPort:      "5342",
		johnFork:        strings.HasPrefix(string(b), "ASUSWRT-Merlin-LTS"),
	}, true
}

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

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
	}
	return nil
}

func readPostConf(path string) string {
	f, err := os.Open(path)
	if err != nil {
		return ""
	}
	defer f.Close()
	s := bufio.NewScanner(f)
	var buf []byte
	for s.Scan() {
		line := s.Bytes()
		// Remove nextdns script head if found
		if bytes.Equal(line, []byte("## NextDNS END")) {
			buf = nil
			continue
		}
		buf = append(buf, line...)
		buf = append(buf, '\n')
	}
	buf = bytes.TrimLeft(buf, "\n")
	return string(buf)
}

func (r *Router) Setup() error {
	if err := internal.WriteTemplate(r.DNSMasqPath, tmpl, r, 0755); err != nil {
		return err
	}
	// Restart dnsmasq service to apply changes.
	if err := exec.Command("service", "restart_dnsmasq").Run(); err != nil {
		return fmt.Errorf("service restart_dnsmasq: %v", err)
	}

	return nil
}

func (r *Router) Restore() error {
	var err error
	if r.CurrentPostConf != "" {
		err = os.WriteFile(r.DNSMasqPath, []byte(r.CurrentPostConf), 0755)
	} else {
		err = os.Remove(r.DNSMasqPath)
		if os.IsNotExist(err) {
			err = nil
		}
	}
	if err != nil {
		return fmt.Errorf("restore %s: %v", r.DNSMasqPath, err)
	}

	// Restart dnsmasq service to apply changes.
	if err := exec.Command("service", "restart_dnsmasq").Run(); err != nil {
		return fmt.Errorf("service restart_dnsmasq: %v", err)
	}
	return nil
}

var tmpl = `#!/bin/sh
# Configuration generated by NextDNS

CONFIG="$1"
. /usr/sbin/helper.sh

if [ -f /tmp/nextdns.pid ] && [ -d "/proc/$(sed -n '1p' /tmp/nextdns.pid)" ]; then
	pc_append "no-resolv" "$CONFIG"
	pc_delete "servers-file" "$CONFIG"           # disconnect dnsmasq from WAN DNS settings
	pc_delete "resolv-file" "$CONFIG"            # disconnect dnsmasq from WAN DNS settings
	pc_append "server=127.0.0.1#5342" "$CONFIG"  # point dnsmasq to NextDNS listener IP:port
	pc_delete "stop-dns-rebind" "$CONFIG"        # disable DNS rebind if enabled
	pc_delete "trust-anchor=" "$CONFIG"          # disable DNSSEC
	pc_delete "dnssec" "$CONFIG"                 # disable DNSSEC
	{{- if .CacheEnabled}}
	pc_delete "cache-size" "$CONFIG"
	pc_append "cache-size=0" "$CONFIG"           # let nextdns handle caching
	{{- end}}
	{{- if .ClientReporting}}
	pc_append "add-mac" "$CONFIG"
	pc_append "add-subnet=32,128" "$CONFIG"
	{{- end}}
	exit 0
fi

## NextDNS END
{{.CurrentPostConf -}}
`
