package resolver

import (
	"context"
	"net"
	"strings"

	"github.com/0xERR0R/blocky/config"
	"github.com/0xERR0R/blocky/model"
	"github.com/miekg/dns"
)

type sudnHandler = func(request *model.Request, cfg *config.SUDN) *model.Response

//nolint:gochecknoglobals
var (
	loopbackV4 = net.ParseIP("127.0.0.1")
	loopbackV6 = net.IPv6loopback

	// See Wikipedia for an up-to-date reference:
	// https://en.wikipedia.org/wiki/Special-use_domain_name
	sudnHandlers = map[string]sudnHandler{
		// RFC 6761
		// https://www.rfc-editor.org/rfc/rfc6761
		//
		// Section 6.1
		"10.in-addr.arpa.":      sudnNXDomain,
		"21.172.in-addr.arpa.":  sudnNXDomain,
		"26.172.in-addr.arpa.":  sudnNXDomain,
		"16.172.in-addr.arpa.":  sudnNXDomain,
		"22.172.in-addr.arpa.":  sudnNXDomain,
		"27.172.in-addr.arpa.":  sudnNXDomain,
		"17.172.in-addr.arpa.":  sudnNXDomain,
		"30.172.in-addr.arpa.":  sudnNXDomain,
		"28.172.in-addr.arpa.":  sudnNXDomain,
		"18.172.in-addr.arpa.":  sudnNXDomain,
		"23.172.in-addr.arpa.":  sudnNXDomain,
		"29.172.in-addr.arpa.":  sudnNXDomain,
		"19.172.in-addr.arpa.":  sudnNXDomain,
		"24.172.in-addr.arpa.":  sudnNXDomain,
		"31.172.in-addr.arpa.":  sudnNXDomain,
		"20.172.in-addr.arpa.":  sudnNXDomain,
		"25.172.in-addr.arpa.":  sudnNXDomain,
		"168.192.in-addr.arpa.": sudnNXDomain,
		// Section 6.2
		"test.": sudnNXDomain,
		// Section 6.3
		"localhost.": sudnLocalhost,
		// Section 6.4
		"invalid.": sudnNXDomain,
		// Section 6.5
		"example.":     nil,
		"example.com.": nil,
		"example.net.": nil,
		"example.org.": nil,

		// RFC 6762
		// https://www.rfc-editor.org/rfc/rfc6762
		//
		// mDNS is not implemented, so just return NXDOMAIN
		//
		// Section 3
		"local.": sudnNXDomain,
		// Section 12
		"254.169.in-addr.arpa.": sudnNXDomain, // also section 4
		"8.e.f.ip6.arpa.":       sudnNXDomain,
		"9.e.f.ip6.arpa.":       sudnNXDomain,
		"a.e.f.ip6.arpa.":       sudnNXDomain,
		"b.e.f.ip6.arpa.":       sudnNXDomain,
		// Appendix G
		"intranet.": sudnRFC6762AppendixG,
		"internal.": sudnRFC6762AppendixG,
		"private.":  sudnRFC6762AppendixG,
		"corp.":     sudnRFC6762AppendixG,
		"home.":     sudnRFC6762AppendixG,
		"lan.":      sudnRFC6762AppendixG,

		// RFC 7686
		// https://www.rfc-editor.org/rfc/rfc7686
		"onion.": sudnNXDomain,

		// RFC 8375
		// https://www.rfc-editor.org/rfc/rfc8375
		//
		// Section 4
		"home.arpa.": sudnHomeArpa,
	}
)

type SpecialUseDomainNamesResolver struct {
	NextResolver
	typed
	configurable[*config.SUDN]
}

func NewSpecialUseDomainNamesResolver(cfg config.SUDN) *SpecialUseDomainNamesResolver {
	return &SpecialUseDomainNamesResolver{
		typed:        withType("special_use_domains"),
		configurable: withConfig(&cfg),
	}
}

func (r *SpecialUseDomainNamesResolver) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) {
	if !r.cfg.Enable {
		return r.next.Resolve(ctx, request)
	}

	handler := r.handler(request)
	if handler != nil {
		resp := handler(request, r.cfg)
		if resp != nil {
			return resp, nil
		}
	}

	return r.next.Resolve(ctx, request)
}

func (r *SpecialUseDomainNamesResolver) handler(request *model.Request) sudnHandler {
	q := request.Req.Question[0]
	domain := q.Name

	for {
		handler, ok := sudnHandlers[domain]
		if ok {
			return handler
		}

		_, after, ok := strings.Cut(domain, ".")
		if !ok {
			return nil
		}

		domain = after
	}
}

func newSUDNResponse(response *model.Request, rcode int) *model.Response {
	return newResponse(response, rcode, model.ResponseTypeSPECIAL, "Special-Use Domain Name")
}

func sudnNXDomain(request *model.Request, _ *config.SUDN) *model.Response {
	return newSUDNResponse(request, dns.RcodeNameError)
}

func sudnLocalhost(request *model.Request, cfg *config.SUDN) *model.Response {
	q := request.Req.Question[0]

	var rr dns.RR

	switch q.Qtype {
	case dns.TypeA:
		rr = &dns.A{A: loopbackV4}
	case dns.TypeAAAA:
		rr = &dns.AAAA{AAAA: loopbackV6}
	default:
		return sudnNXDomain(request, cfg)
	}

	*rr.Header() = dns.RR_Header{
		Name:   q.Name,
		Rrtype: q.Qtype,
		Class:  dns.ClassINET,
		Ttl:    0,
	}

	response := newSUDNResponse(request, dns.RcodeSuccess)
	response.Res.Answer = []dns.RR{rr}

	return response
}

func sudnRFC6762AppendixG(request *model.Request, cfg *config.SUDN) *model.Response {
	if !cfg.RFC6762AppendixG {
		return nil
	}

	return sudnNXDomain(request, cfg)
}

func sudnHomeArpa(request *model.Request, cfg *config.SUDN) *model.Response {
	if request.Req.Question[0].Qtype == dns.TypeDS {
		// DS queries must be forwarded
		return nil
	}

	return sudnNXDomain(request, cfg)
}
