/*
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 controller

import (
	"testing"

	"github.com/stretchr/testify/assert"

	"sigs.k8s.io/external-dns/endpoint"
	"sigs.k8s.io/external-dns/internal/testutils"
	"sigs.k8s.io/external-dns/pkg/apis/externaldns"
	"sigs.k8s.io/external-dns/plan"
	registryfactory "sigs.k8s.io/external-dns/registry/factory"
)

func TestVerifyARecords(t *testing.T) {
	testControllerFiltersDomains(
		t,
		[]*endpoint.Endpoint{
			{
				DNSName:    "create-record.used.tld",
				RecordType: endpoint.RecordTypeA,
				Targets:    endpoint.Targets{"1.2.3.4"},
			},
			{
				DNSName:    "some-record.used.tld",
				RecordType: endpoint.RecordTypeA,
				Targets:    endpoint.Targets{"8.8.8.8"},
			},
		},
		endpoint.NewDomainFilter([]string{"used.tld"}),
		[]*endpoint.Endpoint{
			{
				DNSName:    "some-record.used.tld",
				RecordType: endpoint.RecordTypeA,
				Targets:    endpoint.Targets{"8.8.8.8"},
			},
			{
				DNSName:    "create-record.used.tld",
				RecordType: endpoint.RecordTypeA,
				Targets:    endpoint.Targets{"1.2.3.4"},
			},
		},
		[]*plan.Changes{},
	)

	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "a"})

	testControllerFiltersDomains(
		t,
		[]*endpoint.Endpoint{
			{
				DNSName:    "some-record.1.used.tld",
				RecordType: endpoint.RecordTypeA,
				Targets:    endpoint.Targets{"1.2.3.4"},
			},
			{
				DNSName:    "some-record.2.used.tld",
				RecordType: endpoint.RecordTypeA,
				Targets:    endpoint.Targets{"8.8.8.8"},
			},
			{
				DNSName:    "some-record.3.used.tld",
				RecordType: endpoint.RecordTypeA,
				Targets:    endpoint.Targets{"24.24.24.24"},
			},
		},
		endpoint.NewDomainFilter([]string{"used.tld"}),
		[]*endpoint.Endpoint{
			{
				DNSName:    "some-record.1.used.tld",
				RecordType: endpoint.RecordTypeA,
				Targets:    endpoint.Targets{"1.2.3.4"},
			},
			{
				DNSName:    "some-record.2.used.tld",
				RecordType: endpoint.RecordTypeA,
				Targets:    endpoint.Targets{"8.8.8.8"},
			},
		},
		[]*plan.Changes{{
			Create: []*endpoint.Endpoint{
				{
					DNSName:    "some-record.3.used.tld",
					RecordType: endpoint.RecordTypeA,
					Targets:    endpoint.Targets{"24.24.24.24"},
				},
			},
		}},
	)

	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
}

func TestVerifyAAAARecords(t *testing.T) {
	testControllerFiltersDomains(
		t,
		[]*endpoint.Endpoint{
			{
				DNSName:    "create-record.used.tld",
				RecordType: endpoint.RecordTypeAAAA,
				Targets:    endpoint.Targets{"2001:DB8::1"},
			},
			{
				DNSName:    "some-record.used.tld",
				RecordType: endpoint.RecordTypeAAAA,
				Targets:    endpoint.Targets{"2001:DB8::2"},
			},
		},
		endpoint.NewDomainFilter([]string{"used.tld"}),
		[]*endpoint.Endpoint{
			{
				DNSName:    "some-record.used.tld",
				RecordType: endpoint.RecordTypeAAAA,
				Targets:    endpoint.Targets{"2001:DB8::2"},
			},
			{
				DNSName:    "create-record.used.tld",
				RecordType: endpoint.RecordTypeAAAA,
				Targets:    endpoint.Targets{"2001:DB8::1"},
			},
		},
		[]*plan.Changes{},
	)

	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})

	testControllerFiltersDomains(
		t,
		[]*endpoint.Endpoint{
			{
				DNSName:    "some-record.1.used.tld",
				RecordType: endpoint.RecordTypeAAAA,
				Targets:    endpoint.Targets{"2001:DB8::1"},
			},
			{
				DNSName:    "some-record.2.used.tld",
				RecordType: endpoint.RecordTypeAAAA,
				Targets:    endpoint.Targets{"2001:DB8::2"},
			},
			{
				DNSName:    "some-record.3.used.tld",
				RecordType: endpoint.RecordTypeAAAA,
				Targets:    endpoint.Targets{"2001:DB8::3"},
			},
		},
		endpoint.NewDomainFilter([]string{"used.tld"}),
		[]*endpoint.Endpoint{
			{
				DNSName:    "some-record.1.used.tld",
				RecordType: endpoint.RecordTypeAAAA,
				Targets:    endpoint.Targets{"2001:DB8::1"},
			},
			{
				DNSName:    "some-record.2.used.tld",
				RecordType: endpoint.RecordTypeAAAA,
				Targets:    endpoint.Targets{"2001:DB8::2"},
			},
		},
		[]*plan.Changes{{
			Create: []*endpoint.Endpoint{
				{
					DNSName:    "some-record.3.used.tld",
					RecordType: endpoint.RecordTypeAAAA,
					Targets:    endpoint.Targets{"2001:DB8::3"},
				},
			},
		}},
	)

	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
}

func TestARecords(t *testing.T) {
	testControllerFiltersDomains(
		t,
		[]*endpoint.Endpoint{
			{
				DNSName:    "record1.used.tld",
				RecordType: endpoint.RecordTypeA,
				Targets:    endpoint.Targets{"1.2.3.4"},
			},
			{
				DNSName:    "record2.used.tld",
				RecordType: endpoint.RecordTypeA,
				Targets:    endpoint.Targets{"8.8.8.8"},
			},
			{
				DNSName:    "_mysql-svc._tcp.mysql.used.tld",
				RecordType: endpoint.RecordTypeSRV,
				Targets:    endpoint.Targets{"0 50 30007 mysql.used.tld"},
			},
		},
		endpoint.NewDomainFilter([]string{"used.tld"}),
		[]*endpoint.Endpoint{
			{
				DNSName:    "record1.used.tld",
				RecordType: endpoint.RecordTypeA,
				Targets:    endpoint.Targets{"1.2.3.4"},
			},
			{
				DNSName:    "_mysql-svc._tcp.mysql.used.tld",
				RecordType: endpoint.RecordTypeSRV,
				Targets:    endpoint.Targets{"0 50 30007 mysql.used.tld"},
			},
		},
		[]*plan.Changes{{
			Create: []*endpoint.Endpoint{
				{
					DNSName:    "record2.used.tld",
					RecordType: endpoint.RecordTypeA,
					Targets:    endpoint.Targets{"8.8.8.8"},
				},
			},
		}},
	)
	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
}

func TestAAAARecords(t *testing.T) {
	testControllerFiltersDomains(
		t,
		[]*endpoint.Endpoint{
			{
				DNSName:    "record1.used.tld",
				RecordType: endpoint.RecordTypeAAAA,
				Targets:    endpoint.Targets{"2001:DB8::1"},
			},
			{
				DNSName:    "record2.used.tld",
				RecordType: endpoint.RecordTypeAAAA,
				Targets:    endpoint.Targets{"2001:DB8::2"},
			},
			{
				DNSName:    "_mysql-svc._tcp.mysql.used.tld",
				RecordType: endpoint.RecordTypeSRV,
				Targets:    endpoint.Targets{"0 50 30007 mysql.used.tld"},
			},
		},
		endpoint.NewDomainFilter([]string{"used.tld"}),
		[]*endpoint.Endpoint{
			{
				DNSName:    "record1.used.tld",
				RecordType: endpoint.RecordTypeAAAA,
				Targets:    endpoint.Targets{"2001:DB8::1"},
			},
			{
				DNSName:    "_mysql-svc._tcp.mysql.used.tld",
				RecordType: endpoint.RecordTypeSRV,
				Targets:    endpoint.Targets{"0 50 30007 mysql.used.tld"},
			},
		},
		[]*plan.Changes{{
			Create: []*endpoint.Endpoint{
				{
					DNSName:    "record2.used.tld",
					RecordType: endpoint.RecordTypeAAAA,
					Targets:    endpoint.Targets{"2001:DB8::2"},
				},
			},
		}},
	)

	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, sourceRecords.Gauge, map[string]string{"record_type": "a"})
	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, sourceRecords.Gauge, map[string]string{"record_type": "aaaa"})

	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
}

func TestGaugeMetricsWithMixedRecords(t *testing.T) {
	ctrl := newMixedRecordsFixture()

	assert.NoError(t, ctrl.RunOnce(t.Context()))

	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 534, sourceRecords.Gauge, map[string]string{"record_type": "a"})
	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 324, sourceRecords.Gauge, map[string]string{"record_type": "aaaa"})
	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, sourceRecords.Gauge, map[string]string{"record_type": "cname"})
	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 11, sourceRecords.Gauge, map[string]string{"record_type": "srv"})

	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 5334, registryRecords.Gauge, map[string]string{"record_type": "a"})
	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 324, registryRecords.Gauge, map[string]string{"record_type": "aaaa"})
	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, registryRecords.Gauge, map[string]string{"record_type": "mx"})
	testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 43, registryRecords.Gauge, map[string]string{"record_type": "ptr"})
}

func newMixedRecordsFixture() *Controller {
	configuredEndpoints := testutils.GenerateTestEndpointsByType(map[string]int{
		endpoint.RecordTypeA:     534,
		endpoint.RecordTypeAAAA:  324,
		endpoint.RecordTypeCNAME: 2,
		endpoint.RecordTypeTXT:   56,
		endpoint.RecordTypeSRV:   11,
		endpoint.RecordTypeNS:    3,
	})

	providerEndpoints := testutils.GenerateTestEndpointsByType(map[string]int{
		endpoint.RecordTypeA:     5334,
		endpoint.RecordTypeAAAA:  324,
		endpoint.RecordTypeCNAME: 23,
		endpoint.RecordTypeTXT:   6,
		endpoint.RecordTypeSRV:   25,
		endpoint.RecordTypeNS:    1,
		endpoint.RecordTypePTR:   43,
	})

	cfg := externaldns.NewConfig()
	cfg.Registry = externaldns.RegistryNoop
	cfg.ManagedDNSRecordTypes = endpoint.KnownRecordTypes

	source := new(testutils.MockSource)
	source.On("Endpoints").Return(configuredEndpoints, nil)

	provider := &filteredMockProvider{
		RecordsStore: providerEndpoints,
	}
	r, _ := registryfactory.Select(cfg, provider)

	return &Controller{
		Source:             source,
		Registry:           r,
		Policy:             &plan.SyncPolicy{},
		DomainFilter:       endpoint.NewDomainFilter([]string{}),
		ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
	}
}

func BenchmarkGaugeMetricsWithMixedRecords(b *testing.B) {
	ctrl := newMixedRecordsFixture()

	for b.Loop() {
		if err := ctrl.RunOnce(b.Context()); err != nil {
			b.Fatal(err)
		}
	}
}
