/*
Copyright 2025 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package mapper

import (
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"

	"sigs.k8s.io/external-dns/endpoint"
)

var (
	_ NameMapper = AffixNameMapper{}
)

func TestAffixNameMapper_ToEndpointName(t *testing.T) {
	tests := []struct {
		name             string
		mapper           AffixNameMapper
		input            string
		wantEndpointName string
		wantRecordType   string
	}{
		{
			name:             "prefix with A record type in affix",
			mapper:           NewAffixNameMapper("%{record_type}-", "", ""),
			input:            "a-foo.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeA,
		},
		{
			name:             "prefix with AAAA record type in affix",
			mapper:           NewAffixNameMapper("%{record_type}-", "", ""),
			input:            "aaaa-foo.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeAAAA,
		},
		{
			name:             "prefix with CNAME record type in affix",
			mapper:           NewAffixNameMapper("%{record_type}-", "", ""),
			input:            "cname-foo.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeCNAME,
		},
		{
			name:             "prefix with NS record type in affix",
			mapper:           NewAffixNameMapper("%{record_type}-", "", ""),
			input:            "ns-foo.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeNS,
		},
		{
			name:             "prefix with MX record type in affix",
			mapper:           NewAffixNameMapper("%{record_type}-", "", ""),
			input:            "mx-foo.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeMX,
		},
		{
			name:             "prefix with SRV record type in affix",
			mapper:           NewAffixNameMapper("%{record_type}-", "", ""),
			input:            "srv-foo.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeSRV,
		},
		{
			name:             "prefix with PTR record type in affix",
			mapper:           NewAffixNameMapper("%{record_type}-", "", ""),
			input:            "ptr-2.49.168.192.in-addr.arpa",
			wantEndpointName: "2.49.168.192.in-addr.arpa",
			wantRecordType:   endpoint.RecordTypePTR,
		},
		{
			name:             "prefix with NAPTR record type in affix",
			mapper:           NewAffixNameMapper("%{record_type}-", "", ""),
			input:            "naptr-foo.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeNAPTR,
		},
		{
			name:             "suffix with A record type in affix",
			mapper:           NewAffixNameMapper("", "-%{record_type}", ""),
			input:            "foo-a.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeA,
		},
		{
			name:             "suffix with CNAME record type in affix",
			mapper:           NewAffixNameMapper("", "-%{record_type}", ""),
			input:            "foo-cname.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeCNAME,
		},
		{
			name:             "no affix with A record",
			mapper:           NewAffixNameMapper("", "", ""),
			input:            "a-foo.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeA,
		},
		{
			name:             "no affix with AAAA record",
			mapper:           NewAffixNameMapper("", "", ""),
			input:            "aaaa-foo.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeAAAA,
		},
		{
			name:             "no affix with CNAME record",
			mapper:           NewAffixNameMapper("", "", ""),
			input:            "cname-foo.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeCNAME,
		},
		{
			name:             "no affix with NS record",
			mapper:           NewAffixNameMapper("", "", ""),
			input:            "ns-foo.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeNS,
		},
		{
			name:             "no affix with MX record",
			mapper:           NewAffixNameMapper("", "", ""),
			input:            "mx-foo.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeMX,
		},
		{
			name:             "no affix with SRV record",
			mapper:           NewAffixNameMapper("", "", ""),
			input:            "srv-foo.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeSRV,
		},
		{
			name:             "default prefix with PTR record",
			mapper:           NewAffixNameMapper("", "", ""),
			input:            "ptr-2.49.168.192.in-addr.arpa",
			wantEndpointName: "2.49.168.192.in-addr.arpa",
			wantRecordType:   endpoint.RecordTypePTR,
		},
		{
			name:             "no affix with NAPTR record",
			mapper:           NewAffixNameMapper("", "", ""),
			input:            "naptr-foo.example.com",
			wantEndpointName: "foo.example.com",
			wantRecordType:   endpoint.RecordTypeNAPTR,
		},
		{
			name:             "suffix with txt record",
			mapper:           NewAffixNameMapper("", "", ""),
			input:            "txt-foo.example.com",
			wantEndpointName: "txt-foo.example.com",
			wantRecordType:   "",
		},
		{
			name:             "both prefix and suffix set returns empty",
			mapper:           NewAffixNameMapper("pre-", "-suf", ""),
			input:            "pre-a-foo-suf.example.com",
			wantEndpointName: "",
			wantRecordType:   "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotName, gotType := tt.mapper.ToEndpointName(tt.input)
			assert.Equal(t, tt.wantEndpointName, gotName)
			assert.Equal(t, tt.wantRecordType, gotType)
		})
	}

	// Verify all supported records are tested
	testedRecords := make(map[string]bool)
	for _, tt := range tests {
		if tt.wantRecordType != "" {
			testedRecords[tt.wantRecordType] = true
		}
	}

	for _, recordType := range supportedRecords {
		assert.True(t, testedRecords[recordType], "Record type %s is in supportedRecords but not tested in TestAffixNameMapper_ToEndpointName", recordType)
	}
}

func TestAffixNameMapper_ToTXTName(t *testing.T) {
	tests := []struct {
		name        string
		mapper      AffixNameMapper
		dns         string
		recordType  string
		wantTXTName string
	}{
		{
			name:        "prefix with A record type in affix",
			mapper:      NewAffixNameMapper("%{record_type}-", "", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeA,
			wantTXTName: "a-foo.example.com",
		},
		{
			name:        "prefix with AAAA record type in affix",
			mapper:      NewAffixNameMapper("%{record_type}-", "", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeAAAA,
			wantTXTName: "aaaa-foo.example.com",
		},
		{
			name:        "prefix with CNAME record type in affix",
			mapper:      NewAffixNameMapper("%{record_type}-", "", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeCNAME,
			wantTXTName: "cname-foo.example.com",
		},
		{
			name:        "prefix with NS record type in affix",
			mapper:      NewAffixNameMapper("%{record_type}-", "", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeNS,
			wantTXTName: "ns-foo.example.com",
		},
		{
			name:        "prefix with MX record type in affix",
			mapper:      NewAffixNameMapper("%{record_type}-", "", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeMX,
			wantTXTName: "mx-foo.example.com",
		},
		{
			name:        "prefix with SRV record type in affix",
			mapper:      NewAffixNameMapper("%{record_type}-", "", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeSRV,
			wantTXTName: "srv-foo.example.com",
		},
		{
			name:        "prefix with PTR record type in affix",
			mapper:      NewAffixNameMapper("%{record_type}-", "", ""),
			dns:         "2.49.168.192.in-addr.arpa",
			recordType:  endpoint.RecordTypePTR,
			wantTXTName: "ptr-2.49.168.192.in-addr.arpa",
		},
		{
			name:        "prefix with NAPTR record type in affix",
			mapper:      NewAffixNameMapper("%{record_type}-", "", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeNAPTR,
			wantTXTName: "naptr-foo.example.com",
		},
		{
			name:        "suffix with A record type in affix",
			mapper:      NewAffixNameMapper("", "-%{record_type}", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeA,
			wantTXTName: "foo-a.example.com",
		},
		{
			name:        "suffix with CNAME record type in affix",
			mapper:      NewAffixNameMapper("", "-%{record_type}", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeCNAME,
			wantTXTName: "foo-cname.example.com",
		},
		{
			name:        "wildcard replacement with A record",
			mapper:      NewAffixNameMapper("txt-", "", "wild"),
			dns:         "*.example.com",
			recordType:  endpoint.RecordTypeA,
			wantTXTName: "txt-a-wild.example.com",
		},
		{
			name:        "wildcard replacement with MX record",
			mapper:      NewAffixNameMapper("txt-", "", "wild"),
			dns:         "*.example.com",
			recordType:  endpoint.RecordTypeMX,
			wantTXTName: "txt-mx-wild.example.com",
		},
		{
			name:        "no affix with A record",
			mapper:      NewAffixNameMapper("", "", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeA,
			wantTXTName: "a-foo.example.com",
		},
		{
			name:        "no affix with AAAA record",
			mapper:      NewAffixNameMapper("", "", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeAAAA,
			wantTXTName: "aaaa-foo.example.com",
		},
		{
			name:        "no affix with CNAME record",
			mapper:      NewAffixNameMapper("", "", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeCNAME,
			wantTXTName: "cname-foo.example.com",
		},
		{
			name:        "no affix with NS record",
			mapper:      NewAffixNameMapper("", "", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeNS,
			wantTXTName: "ns-foo.example.com",
		},
		{
			name:        "no affix with MX record",
			mapper:      NewAffixNameMapper("", "", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeMX,
			wantTXTName: "mx-foo.example.com",
		},
		{
			name:        "no affix with SRV record",
			mapper:      NewAffixNameMapper("", "", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeSRV,
			wantTXTName: "srv-foo.example.com",
		},
		{
			name:        "default prefix with PTR record",
			mapper:      NewAffixNameMapper("", "", ""),
			dns:         "2.49.168.192.in-addr.arpa",
			recordType:  endpoint.RecordTypePTR,
			wantTXTName: "ptr-2.49.168.192.in-addr.arpa",
		},
		{
			name:        "no affix with NAPTR record",
			mapper:      NewAffixNameMapper("", "", ""),
			dns:         "foo.example.com",
			recordType:  endpoint.RecordTypeNAPTR,
			wantTXTName: "naptr-foo.example.com",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := tt.mapper.ToTXTName(tt.dns, tt.recordType)
			assert.Equal(t, tt.wantTXTName, got)
		})
	}

	// Verify all supported records are tested
	testedRecords := make(map[string]bool)
	for _, tt := range tests {
		testedRecords[tt.recordType] = true
	}

	for _, recordType := range supportedRecords {
		assert.True(t, testedRecords[recordType], "Record type %s is in supportedRecords but not tested in TestAffixNameMapper_ToTXTName", recordType)
	}
}

func TestAffixNameMapper_RecordTypeInAffix(t *testing.T) {
	tests := []struct {
		name   string
		mapper AffixNameMapper
		want   bool
	}{
		{
			name:   "prefix contains record type",
			mapper: NewAffixNameMapper("%{record_type}-", "", ""),
			want:   true,
		},
		{
			name:   "suffix contains record type",
			mapper: NewAffixNameMapper("", "-%{record_type}", ""),
			want:   true,
		},
		{
			name:   "no record type in affix",
			mapper: NewAffixNameMapper("txt-", "-txt", ""),
			want:   false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := tt.mapper.recordTypeInAffix()
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestToEndpointNameNewTXT(t *testing.T) {
	tests := []struct {
		name       string
		mapper     NameMapper
		domain     string
		txtDomain  string
		recordType string
	}{
		{
			name:       "prefix",
			mapper:     NewAffixNameMapper("foo", "", ""),
			domain:     "example.com",
			recordType: "A",
			txtDomain:  "fooa-example.com",
		},
		{
			name:       "suffix",
			mapper:     NewAffixNameMapper("", "foo", ""),
			domain:     "example",
			recordType: "AAAA",
			txtDomain:  "aaaa-examplefoo",
		},
		{
			name:       "suffix",
			mapper:     NewAffixNameMapper("", "foo", ""),
			domain:     "example.com",
			recordType: "AAAA",
			txtDomain:  "aaaa-examplefoo.com",
		},
		{
			name:       "prefix with dash",
			mapper:     NewAffixNameMapper("foo-", "", ""),
			domain:     "example.com",
			recordType: "A",
			txtDomain:  "foo-a-example.com",
		},
		{
			name:       "suffix with dash",
			mapper:     NewAffixNameMapper("", "-foo", ""),
			domain:     "example.com",
			recordType: "CNAME",
			txtDomain:  "cname-example-foo.com",
		},
		{
			name:       "prefix with dot",
			mapper:     NewAffixNameMapper("foo.", "", ""),
			domain:     "example.com",
			recordType: "CNAME",
			txtDomain:  "foo.cname-example.com",
		},
		{
			name:       "suffix with dot",
			mapper:     NewAffixNameMapper("", ".foo", ""),
			domain:     "example.com",
			recordType: "CNAME",
			txtDomain:  "cname-example.foo.com",
		},
		{
			name:       "prefix with multiple dots",
			mapper:     NewAffixNameMapper("foo.bar.", "", ""),
			domain:     "example.com",
			recordType: "CNAME",
			txtDomain:  "foo.bar.cname-example.com",
		},
		{
			name:       "suffix with multiple dots",
			mapper:     NewAffixNameMapper("", ".foo.bar.test", ""),
			domain:     "example.com",
			recordType: "CNAME",
			txtDomain:  "cname-example.foo.bar.test.com",
		},
		{
			name:       "templated prefix",
			mapper:     NewAffixNameMapper("%{record_type}-foo", "", ""),
			domain:     "example.com",
			recordType: "A",
			txtDomain:  "a-fooexample.com",
		},
		{
			name:       "templated suffix",
			mapper:     NewAffixNameMapper("", "foo-%{record_type}", ""),
			domain:     "example.com",
			recordType: "A",
			txtDomain:  "examplefoo-a.com",
		},
		{
			name:       "templated prefix with dot",
			mapper:     NewAffixNameMapper("%{record_type}foo.", "", ""),
			domain:     "example.com",
			recordType: "CNAME",
			txtDomain:  "cnamefoo.example.com",
		},
		{
			name:       "templated suffix with dot",
			mapper:     NewAffixNameMapper("", ".foo%{record_type}", ""),
			domain:     "example.com",
			recordType: "A",
			txtDomain:  "example.fooa.com",
		},
		{
			name:       "templated prefix with multiple dots",
			mapper:     NewAffixNameMapper("bar.%{record_type}.foo.", "", ""),
			domain:     "example.com",
			recordType: "CNAME",
			txtDomain:  "bar.cname.foo.example.com",
		},
		{
			name:       "templated suffix with multiple dots",
			mapper:     NewAffixNameMapper("", ".foo%{record_type}.bar", ""),
			domain:     "example.com",
			recordType: "A",
			txtDomain:  "example.fooa.bar.com",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			txtDomain := tc.mapper.ToTXTName(tc.domain, tc.recordType)
			assert.Equal(t, tc.txtDomain, txtDomain)

			domain, _ := tc.mapper.ToEndpointName(txtDomain)
			assert.Equal(t, tc.domain, domain)
		})
	}
}

func TestDropPrefix(t *testing.T) {
	mapper := NewAffixNameMapper("foo-%{record_type}-", "", "")
	expectedOutput := "test.example.com"

	tests := []string{
		"foo-cname-test.example.com",
		"foo-a-test.example.com",
		"foo--test.example.com",
	}

	for _, tc := range tests {
		t.Run(tc, func(t *testing.T) {
			actualOutput, _ := mapper.dropAffixExtractType(tc)
			assert.Equal(t, expectedOutput, actualOutput)
		})
	}
}

func TestDropSuffix(t *testing.T) {
	mapper := NewAffixNameMapper("", "-%{record_type}-foo", "")
	expectedOutput := "test.example.com"

	tests := []string{
		"test-a-foo.example.com",
		"test--foo.example.com",
	}

	for _, tc := range tests {
		t.Run(tc, func(t *testing.T) {
			r := strings.SplitN(tc, ".", 2)
			rClean, _ := mapper.dropAffixExtractType(r[0])
			actualOutput := rClean + "." + r[1]
			assert.Equal(t, expectedOutput, actualOutput)
		})
	}
}

func TestDropAffixExtractType_NoMatch(t *testing.T) {
	tests := []struct {
		name   string
		mapper AffixNameMapper
		input  string
	}{
		{
			name:   "prefix mapper input missing prefix",
			mapper: NewAffixNameMapper("foo-", "", ""),
			input:  "bar.example.com",
		},
		{
			name:   "suffix mapper input missing suffix",
			mapper: NewAffixNameMapper("", "-foo", ""),
			input:  "bar.example.com",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotName, gotType := tt.mapper.dropAffixExtractType(tt.input)
			assert.Empty(t, gotName)
			assert.Empty(t, gotType)
		})
	}
}

func TestExtractRecordTypeDefaultPosition(t *testing.T) {
	tests := []struct {
		input        string
		expectedName string
		expectedType string
	}{
		{
			input:        "ns-zone.example.com",
			expectedName: "zone.example.com",
			expectedType: "NS",
		},
		{
			input:        "aaaa-zone.example.com",
			expectedName: "zone.example.com",
			expectedType: "AAAA",
		},
		{
			input:        "ptr-zone.example.com",
			expectedName: "zone.example.com",
			expectedType: "PTR",
		},
		{
			input:        "zone.example.com",
			expectedName: "zone.example.com",
			expectedType: "",
		},
	}

	for _, tc := range tests {
		t.Run(tc.input, func(t *testing.T) {
			actualName, actualType := extractRecordTypeDefaultPosition(tc.input)
			assert.Equal(t, tc.expectedName, actualName)
			assert.Equal(t, tc.expectedType, actualType)
		})
	}
}
