package file

import (
	"context"
	"strings"
	"testing"

	"github.com/coredns/coredns/plugin/pkg/dnstest"
	"github.com/coredns/coredns/plugin/test"

	"github.com/miekg/dns"
)

var svcbAuth = []dns.RR{
	test.NS("example.com.	1800	IN	NS	ns.example.com."),
}

var svcbTestCases = []test.Case{
	{
		// Basic SVCB query with additional section glue for in-bailiwick target
		Qname: "_8443._foo.example.com.", Qtype: dns.TypeSVCB,
		Answer: []dns.RR{
			test.SVCB("_8443._foo.example.com. 1800 IN SVCB 1 svc-target.example.com. alpn=\"h2,h3\" port=\"8443\""),
		},
		Ns: svcbAuth,
		Extra: []dns.RR{
			test.A("svc-target.example.com. 1800 IN A 192.0.2.10"),
			test.AAAA("svc-target.example.com. 1800 IN AAAA 2001:db8::10"),
		},
	},
	{
		// Basic HTTPS query with additional section glue for in-bailiwick target
		Qname: "www.example.com.", Qtype: dns.TypeHTTPS,
		Answer: []dns.RR{
			test.HTTPS("www.example.com. 1800 IN HTTPS 1 svc-target.example.com. alpn=\"h2,h3\""),
		},
		Ns: svcbAuth,
		Extra: []dns.RR{
			test.A("svc-target.example.com. 1800 IN A 192.0.2.10"),
			test.AAAA("svc-target.example.com. 1800 IN AAAA 2001:db8::10"),
		},
	},
	{
		// SVCB AliasMode (Priority=0) — glue still added for in-bailiwick target
		Qname: "_alias._foo.example.com.", Qtype: dns.TypeSVCB,
		Answer: []dns.RR{
			test.SVCB("_alias._foo.example.com. 1800 IN SVCB 0 svc-target.example.com."),
		},
		Ns: svcbAuth,
		Extra: []dns.RR{
			test.A("svc-target.example.com. 1800 IN A 192.0.2.10"),
			test.AAAA("svc-target.example.com. 1800 IN AAAA 2001:db8::10"),
		},
	},
	{
		// Wildcard SVCB expansion — glue for the in-bailiwick target is added to
		// the additional section, same as for non-wildcard answers (issue #6629).
		Qname: "_http._tcp.example.com.", Qtype: dns.TypeSVCB,
		Answer: []dns.RR{
			test.SVCB("_http._tcp.example.com. 1800 IN SVCB 1 svc-target.example.com. port=\"443\""),
		},
		Ns: svcbAuth,
		Extra: []dns.RR{
			test.A("svc-target.example.com. 1800 IN A 192.0.2.10"),
			test.AAAA("svc-target.example.com. 1800 IN AAAA 2001:db8::10"),
		},
	},
	{
		// NoData: existing name, no SVCB record
		Qname: "svc-target.example.com.", Qtype: dns.TypeSVCB,
		Ns: []dns.RR{
			test.SOA("example.com.	1800	IN	SOA	ns.example.com. admin.example.com. 2024010100 14400 3600 604800 14400"),
		},
	},
	{
		// NXDOMAIN
		Qname: "nonexistent.example.com.", Qtype: dns.TypeSVCB,
		Rcode: dns.RcodeNameError,
		Ns: []dns.RR{
			test.SOA("example.com.	1800	IN	SOA	ns.example.com. admin.example.com. 2024010100 14400 3600 604800 14400"),
		},
	},
}

func TestLookupSVCB(t *testing.T) {
	zone, err := Parse(strings.NewReader(dbSVCBExample), testSVCBOrigin, "stdin", 0)
	if err != nil {
		t.Fatalf("Expected no error when reading zone, got %q", err)
	}

	fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testSVCBOrigin: zone}, Names: []string{testSVCBOrigin}}}
	ctx := context.TODO()

	for _, tc := range svcbTestCases {
		m := tc.Msg()

		rec := dnstest.NewRecorder(&test.ResponseWriter{})
		_, err := fm.ServeDNS(ctx, rec, m)
		if err != nil {
			t.Errorf("Expected no error for %q/%d, got %v", tc.Qname, tc.Qtype, err)
			continue
		}

		resp := rec.Msg
		if err := test.SortAndCheck(resp, tc); err != nil {
			t.Errorf("Test %q/%d: %v", tc.Qname, tc.Qtype, err)
		}
	}
}

func TestSVCBTargetNormalization(t *testing.T) {
	// Zone with mixed-case SVCB target — should be lowercased on insert
	const dbMixedCase = `
$TTL 30M
$ORIGIN example.com.
@	IN SOA ns.example.com. admin.example.com. ( 2024010100 14400 3600 604800 14400 )
	IN NS  ns.example.com.
ns	IN A   192.0.2.1
svc	IN A   192.0.2.10
_foo	IN SVCB 1 SVC.Example.COM. alpn=h2
`
	zone, err := Parse(strings.NewReader(dbMixedCase), testSVCBOrigin, "stdin", 0)
	if err != nil {
		t.Fatalf("Expected no error when reading zone, got %q", err)
	}

	fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testSVCBOrigin: zone}, Names: []string{testSVCBOrigin}}}
	ctx := context.TODO()

	m := new(dns.Msg)
	m.SetQuestion("_foo.example.com.", dns.TypeSVCB)

	rec := dnstest.NewRecorder(&test.ResponseWriter{})
	_, err = fm.ServeDNS(ctx, rec, m)
	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}

	resp := rec.Msg
	if len(resp.Answer) != 1 {
		t.Fatalf("Expected 1 answer, got %d", len(resp.Answer))
	}

	svcb, ok := resp.Answer[0].(*dns.SVCB)
	if !ok {
		t.Fatalf("Expected SVCB record, got %T", resp.Answer[0])
	}
	if svcb.Target != "svc.example.com." {
		t.Errorf("Expected lowercased target %q, got %q", "svc.example.com.", svcb.Target)
	}

	// Verify additional section contains glue (target was normalized so lookup works)
	if len(resp.Extra) != 1 {
		t.Fatalf("Expected 1 extra record (A glue), got %d", len(resp.Extra))
	}
}

func TestSVCBPrivateKeys(t *testing.T) {
	// Test DNS-AID private-use SvcParamKeys (65400-65408) round-trip
	zone, err := Parse(strings.NewReader(dbSVCBExample), testSVCBOrigin, "stdin", 0)
	if err != nil {
		t.Fatalf("Expected no error when reading zone, got %q", err)
	}

	fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testSVCBOrigin: zone}, Names: []string{testSVCBOrigin}}}
	ctx := context.TODO()

	m := new(dns.Msg)
	m.SetQuestion("_dnsaid.example.com.", dns.TypeHTTPS)

	rec := dnstest.NewRecorder(&test.ResponseWriter{})
	_, err = fm.ServeDNS(ctx, rec, m)
	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}

	resp := rec.Msg
	if len(resp.Answer) != 1 {
		t.Fatalf("Expected 1 answer, got %d", len(resp.Answer))
	}

	https, ok := resp.Answer[0].(*dns.HTTPS)
	if !ok {
		t.Fatalf("Expected HTTPS record, got %T", resp.Answer[0])
	}

	// Verify private-use keys survived the round-trip
	rr := https.String()
	for _, key := range []string{"key65400=", "key65401=", "key65406="} {
		if !strings.Contains(rr, key) {
			t.Errorf("Expected response to contain %s, got: %s", key, rr)
		}
	}
}

const testSVCBOrigin = "example.com."

const dbSVCBExample = `
$TTL 30M
$ORIGIN example.com.
@	IN SOA	ns.example.com. admin.example.com. (
			2024010100 ; serial
			14400      ; refresh (4 hours)
			3600       ; retry (1 hour)
			604800     ; expire (1 week)
			14400      ; minimum (4 hours)
			)
	IN NS	ns.example.com.

; A/AAAA records for glue
ns		IN A	192.0.2.1
svc-target	IN A	192.0.2.10
		IN AAAA	2001:db8::10

; SVCB ServiceMode with in-bailiwick target (tests additional section processing)
_8443._foo	IN SVCB	1 svc-target.example.com. alpn=h2,h3 port=8443

; HTTPS ServiceMode with in-bailiwick target
www		IN HTTPS 1 svc-target.example.com. alpn=h2,h3

; SVCB AliasMode (Priority=0)
_alias._foo	IN SVCB	0 svc-target.example.com.

; Wildcard SVCB
*._tcp		IN SVCB	1 svc-target.example.com. port=443

; DNS-AID private-use keys (65400-65408)
_dnsaid		IN HTTPS 1 svc-target.example.com. alpn=h2 key65400="https://aid.example.com/cap" key65401="sha256:e3b0c44298fc" key65406="apphub-psc"
`
