package diff2

import (
	"testing"

	"github.com/StackExchange/dnscontrol/v4/models"
)

func makeTestRecord(name, rtype, target, domain string) *models.RecordConfig {
	rc := &models.RecordConfig{
		Type: rtype,
	}
	rc.SetLabel(name, domain)
	if rtype == "TXT" {
		rc.SetTargetTXT(target)
	} else {
		rc.SetTarget(target)
	}
	return rc
}

func TestIsExternalDNSTxtRecord(t *testing.T) {
	domain := "example.com"

	tests := []struct {
		name           string
		record         *models.RecordConfig
		wantIsExtDNS   bool
		wantLabel      string
		wantRecordType string
	}{
		{
			name:           "external-dns A record TXT",
			record:         makeTestRecord("a-myapp", "TXT", "heritage=external-dns,external-dns/owner=my-cluster,external-dns/resource=ingress/default/myapp", domain),
			wantIsExtDNS:   true,
			wantLabel:      "myapp",
			wantRecordType: "A",
		},
		{
			name:           "external-dns AAAA record TXT",
			record:         makeTestRecord("aaaa-myapp", "TXT", "heritage=external-dns,external-dns/owner=my-cluster", domain),
			wantIsExtDNS:   true,
			wantLabel:      "myapp",
			wantRecordType: "AAAA",
		},
		{
			name:           "external-dns CNAME record TXT",
			record:         makeTestRecord("cname-www", "TXT", "heritage=external-dns,external-dns/owner=default", domain),
			wantIsExtDNS:   true,
			wantLabel:      "www",
			wantRecordType: "CNAME",
		},
		{
			name:           "external-dns apex A record TXT",
			record:         makeTestRecord("a-", "TXT", "heritage=external-dns,external-dns/owner=k8s", domain),
			wantIsExtDNS:   true,
			wantLabel:      "@",
			wantRecordType: "A",
		},
		{
			name:         "non-external-dns TXT record",
			record:       makeTestRecord("myapp", "TXT", "some random txt content", domain),
			wantIsExtDNS: false,
		},
		{
			name:         "A record (not TXT)",
			record:       makeTestRecord("myapp", "A", "1.2.3.4", domain),
			wantIsExtDNS: false,
		},
		{
			name:         "SPF record",
			record:       makeTestRecord("@", "TXT", "v=spf1 include:_spf.google.com ~all", domain),
			wantIsExtDNS: false,
		},
		{
			name:         "DKIM record",
			record:       makeTestRecord("selector._domainkey", "TXT", "v=DKIM1; k=rsa; p=MIGfMA0G...", domain),
			wantIsExtDNS: false,
		},
		{
			name:           "external-dns with quoted heritage",
			record:         makeTestRecord("a-test", "TXT", "\"heritage=external-dns,external-dns/owner=test\"", domain),
			wantIsExtDNS:   true,
			wantLabel:      "test",
			wantRecordType: "A",
		},
		{
			name:           "external-dns legacy format (no prefix)",
			record:         makeTestRecord("legacy-app", "TXT", "heritage=external-dns,external-dns/owner=old-cluster", domain),
			wantIsExtDNS:   true,
			wantLabel:      "legacy-app",
			wantRecordType: "", // Empty means match any type
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotIsExtDNS, gotInfo := isExternalDNSTxtRecord(tt.record, domain, "")

			if gotIsExtDNS != tt.wantIsExtDNS {
				t.Errorf("isExternalDNSTxtRecord() isExtDNS = %v, want %v", gotIsExtDNS, tt.wantIsExtDNS)
			}

			if tt.wantIsExtDNS {
				if gotInfo == nil {
					t.Errorf("isExternalDNSTxtRecord() returned nil info for external-dns record")
					return
				}
				if gotInfo.Label != tt.wantLabel {
					t.Errorf("isExternalDNSTxtRecord() label = %q, want %q", gotInfo.Label, tt.wantLabel)
				}
				if gotInfo.RecordType != tt.wantRecordType {
					t.Errorf("isExternalDNSTxtRecord() recordType = %q, want %q", gotInfo.RecordType, tt.wantRecordType)
				}
			}
		})
	}
}

func TestParseExternalDNSTxtLabel(t *testing.T) {
	tests := []struct {
		name           string
		label          string
		wantLabel      string
		wantRecordType string
	}{
		{
			name:           "A record prefix",
			label:          "a-myapp",
			wantLabel:      "myapp",
			wantRecordType: "A",
		},
		{
			name:           "AAAA record prefix",
			label:          "aaaa-myapp",
			wantLabel:      "myapp",
			wantRecordType: "AAAA",
		},
		{
			name:           "CNAME record prefix",
			label:          "cname-www",
			wantLabel:      "www",
			wantRecordType: "CNAME",
		},
		{
			name:           "NS record prefix",
			label:          "ns-subdomain",
			wantLabel:      "subdomain",
			wantRecordType: "NS",
		},
		{
			name:           "MX record prefix",
			label:          "mx-mail",
			wantLabel:      "mail",
			wantRecordType: "MX",
		},
		{
			name:           "apex domain (A)",
			label:          "a-",
			wantLabel:      "@",
			wantRecordType: "A",
		},
		{
			name:           "uppercase prefix (should handle case)",
			label:          "A-myapp",
			wantLabel:      "myapp",
			wantRecordType: "A",
		},
		{
			name:           "no recognized prefix (legacy)",
			label:          "myapp",
			wantLabel:      "myapp",
			wantRecordType: "",
		},
		{
			name:           "subdomain with dots in name",
			label:          "a-sub.domain",
			wantLabel:      "sub.domain",
			wantRecordType: "A",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := parseExternalDNSTxtLabel(tt.label, "")

			if got.Label != tt.wantLabel {
				t.Errorf("parseExternalDNSTxtLabel() Label = %q, want %q", got.Label, tt.wantLabel)
			}
			if got.RecordType != tt.wantRecordType {
				t.Errorf("parseExternalDNSTxtLabel() RecordType = %q, want %q", got.RecordType, tt.wantRecordType)
			}
		})
	}
}

func TestFindExternalDNSManagedRecords(t *testing.T) {
	domain := "example.com"

	existing := models.Records{
		// External-dns managed records
		makeTestRecord("a-myapp", "TXT", "heritage=external-dns,external-dns/owner=cluster1", domain),
		makeTestRecord("myapp", "A", "10.0.0.1", domain),
		makeTestRecord("cname-www", "TXT", "heritage=external-dns,external-dns/owner=cluster1", domain),
		makeTestRecord("www", "CNAME", "myapp.example.com.", domain),
		// Non-external-dns records
		makeTestRecord("static", "A", "1.2.3.4", domain),
		makeTestRecord("@", "TXT", "v=spf1 -all", domain),
		makeTestRecord("@", "MX", "mail.example.com.", domain),
	}

	managed := findExternalDNSManagedRecords(existing, domain, "")

	// Check that expected keys are present
	expectedKeys := []string{
		"a-myapp:TXT",   // The TXT record itself
		"myapp:A",       // The A record it manages
		"cname-www:TXT", // The TXT record itself
		"www:CNAME",     // The CNAME record it manages
	}

	for _, key := range expectedKeys {
		if !managed[key] {
			t.Errorf("Expected key %q to be marked as managed", key)
		}
	}

	// Check that non-external-dns records are not marked
	notExpected := []string{
		"static:A",
		"@:TXT",
		"@:MX",
	}

	for _, key := range notExpected {
		if managed[key] {
			t.Errorf("Key %q should not be marked as managed", key)
		}
	}
}

func TestGetExternalDNSIgnoredRecords(t *testing.T) {
	domain := "example.com"

	existing := models.Records{
		// External-dns managed records
		makeTestRecord("a-myapp", "TXT", "heritage=external-dns,external-dns/owner=cluster1", domain),
		makeTestRecord("myapp", "A", "10.0.0.1", domain),
		// Non-external-dns records
		makeTestRecord("static", "A", "1.2.3.4", domain),
		makeTestRecord("@", "TXT", "v=spf1 -all", domain),
	}

	ignored := GetExternalDNSIgnoredRecords(existing, domain, "")

	if len(ignored) != 2 {
		t.Errorf("Expected 2 ignored records, got %d", len(ignored))
	}

	// Verify the ignored records
	foundTXT := false
	foundA := false
	for _, rec := range ignored {
		if rec.GetLabel() == "a-myapp" && rec.Type == "TXT" {
			foundTXT = true
		}
		if rec.GetLabel() == "myapp" && rec.Type == "A" {
			foundA = true
		}
	}

	if !foundTXT {
		t.Error("Expected TXT record a-myapp to be ignored")
	}
	if !foundA {
		t.Error("Expected A record myapp to be ignored")
	}
}

func TestGetExternalDNSIgnoredRecords_NoExternalDNS(t *testing.T) {
	domain := "example.com"

	existing := models.Records{
		makeTestRecord("static", "A", "1.2.3.4", domain),
		makeTestRecord("@", "TXT", "v=spf1 -all", domain),
		makeTestRecord("www", "CNAME", "static.example.com.", domain),
	}

	ignored := GetExternalDNSIgnoredRecords(existing, domain, "")

	if len(ignored) != 0 {
		t.Errorf("Expected 0 ignored records when no external-dns records exist, got %d", len(ignored))
	}
}

func TestGetExternalDNSIgnoredRecords_LegacyFormat(t *testing.T) {
	domain := "example.com"

	// Legacy format: TXT record has same name as the record it manages (no type prefix)
	existing := models.Records{
		makeTestRecord("legacyapp", "TXT", "heritage=external-dns,external-dns/owner=old-cluster", domain),
		makeTestRecord("legacyapp", "A", "10.0.0.1", domain),
		makeTestRecord("legacyapp", "AAAA", "::1", domain),
	}

	ignored := GetExternalDNSIgnoredRecords(existing, domain, "")

	// Legacy format should match the TXT and common record types
	if len(ignored) < 3 {
		t.Errorf("Expected at least 3 ignored records for legacy format, got %d", len(ignored))
	}
}

func TestGetExternalDNSIgnoredRecords_CustomPrefix(t *testing.T) {
	domain := "example.com"

	// Custom prefix format: e.g., "extdns-www" for "www" record
	existing := models.Records{
		makeTestRecord("extdns-www", "TXT", "heritage=external-dns,external-dns/owner=k3s-cluster", domain),
		makeTestRecord("www", "A", "10.0.0.1", domain),
		makeTestRecord("extdns-api", "TXT", "heritage=external-dns,external-dns/owner=k3s-cluster", domain),
		makeTestRecord("api", "CNAME", "app.example.com.", domain),
		// Non-external-dns records
		makeTestRecord("static", "A", "1.2.3.4", domain),
	}

	// Without custom prefix, only the TXT records themselves should be detected
	// (but the A/CNAME won't be linked because "extdns-" isn't a known type prefix)
	ignoredDefault := GetExternalDNSIgnoredRecords(existing, domain, "")

	// With custom prefix, both TXT and their managed records should be detected
	ignoredCustom := GetExternalDNSIgnoredRecords(existing, domain, "extdns-")

	// Custom prefix should find more records
	if len(ignoredCustom) <= len(ignoredDefault) {
		t.Errorf("Expected custom prefix to find more records: default=%d, custom=%d",
			len(ignoredDefault), len(ignoredCustom))
	}

	// Should find: extdns-www:TXT, www:A/AAAA/CNAME/etc, extdns-api:TXT, api:A/AAAA/CNAME/etc
	if len(ignoredCustom) < 4 {
		t.Errorf("Expected at least 4 ignored records with custom prefix, got %d", len(ignoredCustom))
	}
}

func TestParseExternalDNSTxtLabel_CustomPrefix(t *testing.T) {
	tests := []struct {
		name           string
		label          string
		customPrefix   string
		wantLabel      string
		wantRecordType string
	}{
		{
			name:           "custom prefix with record type",
			label:          "extdns-a-myapp",
			customPrefix:   "extdns-",
			wantLabel:      "myapp",
			wantRecordType: "A",
		},
		{
			name:           "custom prefix without record type",
			label:          "extdns-www",
			customPrefix:   "extdns-",
			wantLabel:      "www",
			wantRecordType: "", // No record type in this format
		},
		{
			name:           "custom prefix apex domain",
			label:          "extdns-",
			customPrefix:   "extdns-",
			wantLabel:      "@",
			wantRecordType: "",
		},
		{
			name:           "prefix not found - fallback to legacy",
			label:          "other-www",
			customPrefix:   "extdns-",
			wantLabel:      "other-www",
			wantRecordType: "",
		},
		// Period format tests (--txt-prefix=extdns-%{record_type}.)
		{
			name:           "period format A record",
			label:          "extdns-a.myapp",
			customPrefix:   "extdns-",
			wantLabel:      "myapp",
			wantRecordType: "A",
		},
		{
			name:           "period format AAAA record",
			label:          "extdns-aaaa.myapp",
			customPrefix:   "extdns-",
			wantLabel:      "myapp",
			wantRecordType: "AAAA",
		},
		{
			name:           "period format CNAME record",
			label:          "extdns-cname.www",
			customPrefix:   "extdns-",
			wantLabel:      "www",
			wantRecordType: "CNAME",
		},
		{
			name:           "period format apex A record",
			label:          "extdns-a",
			customPrefix:   "extdns-",
			wantLabel:      "@",
			wantRecordType: "A",
		},
		{
			name:           "period format apex AAAA record",
			label:          "extdns-aaaa",
			customPrefix:   "extdns-",
			wantLabel:      "@",
			wantRecordType: "AAAA",
		},
		{
			name:           "period format apex CNAME record",
			label:          "extdns-cname",
			customPrefix:   "extdns-",
			wantLabel:      "@",
			wantRecordType: "CNAME",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := parseExternalDNSTxtLabel(tt.label, tt.customPrefix)

			if got.Label != tt.wantLabel {
				t.Errorf("parseExternalDNSTxtLabel() Label = %q, want %q", got.Label, tt.wantLabel)
			}
			if got.RecordType != tt.wantRecordType {
				t.Errorf("parseExternalDNSTxtLabel() RecordType = %q, want %q", got.RecordType, tt.wantRecordType)
			}
		})
	}
}

// TestGetExternalDNSIgnoredRecords_PeriodFormat tests the period format used when
// external-dns is configured with --txt-prefix=prefix-%{record_type}.
// This creates TXT records like "extdns-a.www" for "www" A record.
func TestGetExternalDNSIgnoredRecords_PeriodFormat(t *testing.T) {
	domain := "example.com"

	existing := models.Records{
		// Period format TXT records (from --txt-prefix=extdns-%{record_type}.)
		makeTestRecord("extdns-a.www", "TXT", "heritage=external-dns,external-dns/owner=k3s-cluster", domain),
		makeTestRecord("www", "A", "10.0.0.1", domain),
		makeTestRecord("extdns-cname.api", "TXT", "heritage=external-dns,external-dns/owner=k3s-cluster", domain),
		makeTestRecord("api", "CNAME", "app.example.com.", domain),
		// Non-external-dns records
		makeTestRecord("static", "A", "1.2.3.4", domain),
	}

	ignored := GetExternalDNSIgnoredRecords(existing, domain, "extdns-")

	// Should find: extdns-a.www:TXT, www:A, extdns-cname.api:TXT, api:CNAME
	if len(ignored) < 4 {
		t.Errorf("Expected at least 4 ignored records with period format, got %d", len(ignored))
	}

	// Verify specific records are ignored
	found := make(map[string]bool)
	for _, rec := range ignored {
		key := rec.GetLabel() + ":" + rec.Type
		found[key] = true
	}

	expectedKeys := []string{"extdns-a.www:TXT", "www:A", "extdns-cname.api:TXT", "api:CNAME"}
	for _, key := range expectedKeys {
		if !found[key] {
			t.Errorf("Expected record %q to be ignored", key)
		}
	}
}

// TestGetExternalDNSIgnoredRecords_PeriodFormatApex tests apex domain detection
// with the period format (--txt-prefix=prefix-%{record_type}.).
// For apex domains, the TXT label becomes "extdns-a" (just prefix + record type).
func TestGetExternalDNSIgnoredRecords_PeriodFormatApex(t *testing.T) {
	domain := "example.com"

	existing := models.Records{
		// Period format apex A record: TXT at "extdns-a" manages "@" A record
		makeTestRecord("extdns-a", "TXT", "heritage=external-dns,external-dns/owner=k3s-cluster", domain),
		makeTestRecord("@", "A", "10.0.0.1", domain),
		// Period format apex AAAA record
		makeTestRecord("extdns-aaaa", "TXT", "heritage=external-dns,external-dns/owner=k3s-cluster", domain),
		makeTestRecord("@", "AAAA", "::1", domain),
		// Non-external-dns apex records (should not be ignored)
		makeTestRecord("@", "MX", "mail.example.com.", domain),
		makeTestRecord("@", "TXT", "v=spf1 -all", domain),
	}

	ignored := GetExternalDNSIgnoredRecords(existing, domain, "extdns-")

	// Should find: extdns-a:TXT, @:A, extdns-aaaa:TXT, @:AAAA
	if len(ignored) != 4 {
		t.Errorf("Expected 4 ignored records for apex with period format, got %d", len(ignored))
		for _, rec := range ignored {
			t.Logf("  ignored: %s:%s", rec.GetLabel(), rec.Type)
		}
	}

	// Verify specific records are ignored
	found := make(map[string]bool)
	for _, rec := range ignored {
		key := rec.GetLabel() + ":" + rec.Type
		found[key] = true
	}

	expectedKeys := []string{"extdns-a:TXT", "@:A", "extdns-aaaa:TXT", "@:AAAA"}
	for _, key := range expectedKeys {
		if !found[key] {
			t.Errorf("Expected record %q to be ignored", key)
		}
	}

	// Verify MX and SPF TXT are NOT ignored
	notExpectedKeys := []string{"@:MX", "@:TXT"}
	for _, key := range notExpectedKeys {
		if found[key] {
			t.Errorf("Record %q should NOT be ignored", key)
		}
	}
}
