package local

import (
	"context"
	"net"
	"strings"

	"github.com/coredns/coredns/plugin"
	clog "github.com/coredns/coredns/plugin/pkg/log"
	"github.com/coredns/coredns/request"

	"github.com/miekg/dns"
)

var log = clog.NewWithPlugin("local")

// Local is a plugin that returns standard replies for local queries.
type Local struct {
	Next                   plugin.Handler
	disableLocalhostPrefix bool
}

var zones = []string{"localhost.", "0.in-addr.arpa.", "127.in-addr.arpa.", "255.in-addr.arpa."}

func soaFromOrigin(origin string) []dns.RR {
	hdr := dns.RR_Header{Name: origin, Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypeSOA}
	return []dns.RR{&dns.SOA{Hdr: hdr, Ns: "localhost.", Mbox: "root.localhost.", Serial: 1, Refresh: 0, Retry: 0, Expire: 0, Minttl: ttl}}
}

func nsFromOrigin(origin string) []dns.RR {
	hdr := dns.RR_Header{Name: origin, Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypeNS}
	return []dns.RR{&dns.NS{Hdr: hdr, Ns: "localhost."}}
}

// ServeDNS implements the plugin.Handler interface.
func (l Local) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
	state := request.Request{W: w, Req: r}
	qname := state.QName()

	lc := len("localhost.")
	name := state.Name()
	matchesRFCSubdomain := len(name) > lc && strings.HasSuffix(name, ".localhost.")
	matchesLegacyPrefix := len(name) > lc && !l.disableLocalhostPrefix && strings.HasPrefix(name, "localhost.")
	if matchesRFCSubdomain || matchesLegacyPrefix {
		// Intercept special localhost subdomain queries and return loopback addresses for A/AAAA queries.
		log.Debugf("Intercepting localhost query for %q %s, from %s", state.Name(), state.Type(), state.IP())
		LocalhostCount.Inc()
		reply := doLocalhost(state)
		w.WriteMsg(reply)
		return 0, nil
	}

	zone := plugin.Zones(zones).Matches(qname)
	if zone == "" {
		return plugin.NextOrFailure(l.Name(), l.Next, ctx, w, r)
	}

	m := new(dns.Msg)
	m.SetReply(r)
	zone = qname[len(qname)-len(zone):]

	switch q := state.Name(); q {
	case "localhost.", "0.in-addr.arpa.", "127.in-addr.arpa.", "255.in-addr.arpa.":
		switch state.QType() {
		case dns.TypeA:
			if q != "localhost." {
				// nodata
				m.Ns = soaFromOrigin(qname)
				break
			}

			hdr := dns.RR_Header{Name: qname, Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypeA}
			m.Answer = []dns.RR{&dns.A{Hdr: hdr, A: net.ParseIP("127.0.0.1").To4()}}
		case dns.TypeAAAA:
			if q != "localhost." {
				// nodata
				m.Ns = soaFromOrigin(qname)
				break
			}

			hdr := dns.RR_Header{Name: qname, Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypeAAAA}
			m.Answer = []dns.RR{&dns.AAAA{Hdr: hdr, AAAA: net.ParseIP("::1")}}
		case dns.TypeSOA:
			m.Answer = soaFromOrigin(qname)
		case dns.TypeNS:
			m.Answer = nsFromOrigin(qname)
		default:
			// nodata
			m.Ns = soaFromOrigin(qname)
		}
	case "1.0.0.127.in-addr.arpa.":
		switch state.QType() {
		case dns.TypePTR:
			hdr := dns.RR_Header{Name: qname, Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypePTR}
			m.Answer = []dns.RR{&dns.PTR{Hdr: hdr, Ptr: "localhost."}}
		default:
			// nodata
			m.Ns = soaFromOrigin(zone)
		}
	}

	if len(m.Answer) == 0 && len(m.Ns) == 0 {
		m.Ns = soaFromOrigin(zone)
		m.Rcode = dns.RcodeNameError
	}

	w.WriteMsg(m)
	return 0, nil
}

// Name implements the plugin.Handler interface.
func (l Local) Name() string { return "local" }

func doLocalhost(state request.Request) *dns.Msg {
	m := new(dns.Msg)
	m.SetReply(state.Req)
	switch state.QType() {
	case dns.TypeA:
		hdr := dns.RR_Header{Name: state.QName(), Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypeA}
		m.Answer = []dns.RR{&dns.A{Hdr: hdr, A: net.ParseIP("127.0.0.1").To4()}}
	case dns.TypeAAAA:
		hdr := dns.RR_Header{Name: state.QName(), Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypeAAAA}
		m.Answer = []dns.RR{&dns.AAAA{Hdr: hdr, AAAA: net.ParseIP("::1")}}
	default:
		// nodata
		m.Ns = soaFromOrigin(state.QName())
	}
	return m
}

const ttl = 604800
