package namer

import (
	"strings"
	"testing"

	. "github.com/onsi/gomega"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestWithPrefix(t *testing.T) {
	g := NewGomegaWithT(t)

	testCases := []struct {
		name           string
		prefix         string
		inputName      string
		expectedOutput string
		description    string
	}{
		{
			name:           "normal_prefix",
			prefix:         "test",
			inputName:      "name",
			expectedOutput: "test-name",
			description:    "Should add prefix with dash separator",
		},
		{
			name:           "prefix_with_trailing_dash",
			prefix:         "test-",
			inputName:      "name",
			expectedOutput: "test-name",
			description:    "Should not add extra dash when prefix already has trailing dash",
		},
		{
			name:           "empty_prefix",
			prefix:         "",
			inputName:      "name",
			expectedOutput: "name",
			description:    "Should return original name when prefix is empty",
		},
		{
			name:           "empty_name",
			prefix:         "test",
			inputName:      "",
			expectedOutput: "test-",
			description:    "Should handle empty name with prefix",
		},
		{
			name:           "both_empty",
			prefix:         "",
			inputName:      "",
			expectedOutput: "",
			description:    "Should handle both empty prefix and name",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			prefixFunc := WithPrefix(tc.prefix)
			result := prefixFunc(tc.inputName)
			g.Expect(result).To(Equal(tc.expectedOutput), tc.description)
		})
	}
}

func TestWithLimit(t *testing.T) {
	g := NewGomegaWithT(t)

	testCases := []struct {
		name           string
		limit          int
		inputName      string
		expectedLength int
		description    string
	}{
		{
			name:           "normal_limit",
			limit:          10,
			inputName:      "verylongname",
			expectedLength: 1, // limit(10) - hashSize(8) - 1 = 1
			description:    "Should limit name length according to formula",
		},
		{
			name:           "name_within_limit",
			limit:          20,
			inputName:      "short",
			expectedLength: 5, // Original length since it's within limit
			description:    "Should not truncate when name is within limit",
		},
		{
			name:           "exact_limit",
			limit:          17, // hashSize(8) + 1 + 8 chars = 17
			inputName:      "exactlimit",
			expectedLength: 8, // limit(17) - hashSize(8) - 1 = 8
			description:    "Should handle exact limit case",
		},
		{
			name:           "minimum_viable_limit",
			limit:          10, // hashSize(8) + 1 + 1 = 10 is minimum viable
			inputName:      "test",
			expectedLength: 1, // limit(10) - hashSize(8) - 1 = 1
			description:    "Should handle minimum viable limit",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			limitFunc := WithLimit(tc.limit)
			result := limitFunc(tc.inputName)
			g.Expect(len(result)).To(Equal(tc.expectedLength), tc.description)
		})
	}
}

func TestUniqueIdentifier(t *testing.T) {
	g := NewGomegaWithT(t)

	testCases := []struct {
		name        string
		meta        metav1.ObjectMeta
		options     []IdentifierOptions
		description string
		validate    func(string, *GomegaWithT)
	}{
		{
			name: "basic_meta_no_options",
			meta: metav1.ObjectMeta{
				Name:      "test-name",
				Namespace: "test-namespace",
			},
			options:     nil,
			description: "Should generate unique identifier with basic metadata",
			validate: func(result string, g *GomegaWithT) {
				g.Expect(result).To(ContainSubstring("test-namespace-test-name"))
				g.Expect(len(result)).To(Equal(len("test-namespace-test-name") + 1 + hashSize))
				g.Expect(result).To(MatchRegexp(`.*-[a-f0-9]{8}$`))
			},
		},
		{
			name: "name_only_no_namespace",
			meta: metav1.ObjectMeta{
				Name: "test-name",
			},
			options:     nil,
			description: "Should handle metadata without namespace",
			validate: func(result string, g *GomegaWithT) {
				g.Expect(result).To(ContainSubstring("test-name"))
				g.Expect(result).To(MatchRegexp(`test-name-[a-f0-9]{8}$`))
			},
		},
		{
			name: "with_prefix_option",
			meta: metav1.ObjectMeta{
				Name:      "name",
				Namespace: "ns",
			},
			options:     []IdentifierOptions{WithPrefix("prefix")},
			description: "Should apply prefix option",
			validate: func(result string, g *GomegaWithT) {
				g.Expect(result).To(ContainSubstring("prefix-ns-name"))
				g.Expect(result).To(MatchRegexp(`.*-[a-f0-9]{8}$`))
			},
		},
		{
			name: "with_limit_option",
			meta: metav1.ObjectMeta{
				Name:      "verylongname",
				Namespace: "verylongnamespace",
			},
			options:     []IdentifierOptions{WithLimit(20)},
			description: "Should apply limit option",
			validate: func(result string, g *GomegaWithT) {
				g.Expect(len(result)).To(Equal(20))
				g.Expect(result).To(MatchRegexp(`.*-[a-f0-9]{8}$`))
			},
		},
		{
			name: "with_prefix_and_limit",
			meta: metav1.ObjectMeta{
				Name:      "name",
				Namespace: "namespace",
			},
			options:     []IdentifierOptions{WithPrefix("pre"), WithLimit(25)},
			description: "Should apply both prefix and limit options",
			validate: func(result string, g *GomegaWithT) {
				g.Expect(len(result)).To(Equal(25))
				g.Expect(result).To(MatchRegexp(`.*-[a-f0-9]{8}$`))
				g.Expect(result).To(ContainSubstring("pre-"))
			},
		},
		{
			name: "very_long_name_auto_truncated",
			meta: metav1.ObjectMeta{
				Name:      strings.Repeat("a", 100),
				Namespace: strings.Repeat("b", 100),
			},
			options:     nil,
			description: "Should auto-truncate very long names to RFC 1035 limit",
			validate: func(result string, g *GomegaWithT) {
				g.Expect(len(result)).To(Equal(63)) // RFC 1035 label limit
				g.Expect(result).To(MatchRegexp(`.*-[a-f0-9]{8}$`))
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result := UniqueIdentifier(tc.meta, tc.options...)
			tc.validate(result, g)
			t.Logf("Generated identifier: %s (length: %d)", result, len(result))
		})
	}
}

func TestUniqueIdentifierConsistency(t *testing.T) {
	g := NewGomegaWithT(t)

	meta := metav1.ObjectMeta{
		Name:      "test-name",
		Namespace: "test-namespace",
	}

	// Generate the same identifier multiple times
	result1 := UniqueIdentifier(meta)
	result2 := UniqueIdentifier(meta)
	result3 := UniqueIdentifier(meta, WithPrefix("prefix"))

	g.Expect(result1).To(Equal(result2), "Same metadata should produce identical identifiers")
	g.Expect(result1).ToNot(Equal(result3), "Different options should produce different identifiers")
}

func TestUniqueIdentifierDifferentMeta(t *testing.T) {
	g := NewGomegaWithT(t)

	meta1 := metav1.ObjectMeta{Name: "name1", Namespace: "ns1"}
	meta2 := metav1.ObjectMeta{Name: "name2", Namespace: "ns1"}
	meta3 := metav1.ObjectMeta{Name: "name1", Namespace: "ns2"}

	result1 := UniqueIdentifier(meta1)
	result2 := UniqueIdentifier(meta2)
	result3 := UniqueIdentifier(meta3)

	g.Expect(result1).ToNot(Equal(result2), "Different names should produce different identifiers")
	g.Expect(result1).ToNot(Equal(result3), "Different namespaces should produce different identifiers")
	g.Expect(result2).ToNot(Equal(result3), "All different metadata should produce unique identifiers")
}

func TestPscEndpointName(t *testing.T) {
	g := NewGomegaWithT(t)

	testCases := []struct {
		name        string
		meta        metav1.ObjectMeta
		prefix      string
		description string
		validate    func(string, *GomegaWithT)
	}{
		{
			name: "normal_psc_endpoint",
			meta: metav1.ObjectMeta{
				Name:      "endpoint",
				Namespace: "default",
			},
			prefix:      "psc",
			description: "Should generate PSC endpoint name with GCP limit",
			validate: func(result string, g *GomegaWithT) {
				g.Expect(len(result)).To(BeNumerically("<=", GcpPscNameLimit))
				g.Expect(result).To(ContainSubstring("psc-"))
				g.Expect(result).To(MatchRegexp(`.*-[a-f0-9]{8}$`))
			},
		},
		{
			name: "long_psc_endpoint",
			meta: metav1.ObjectMeta{
				Name:      strings.Repeat("verylongendpointname", 5),
				Namespace: strings.Repeat("verylongnamespace", 5),
			},
			prefix:      "very-long-prefix",
			description: "Should enforce GCP PSC name limit for long names",
			validate: func(result string, g *GomegaWithT) {
				g.Expect(len(result)).To(Equal(GcpPscNameLimit))
				g.Expect(result).To(ContainSubstring("very-long-prefix-"))
				g.Expect(result).To(MatchRegexp(`.*-[a-f0-9]{8}$`))
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result := PscEndpointName(tc.meta, tc.prefix)
			tc.validate(result, g)
			t.Logf("Generated PSC endpoint name: %s (length: %d)", result, len(result))
		})
	}
}

func TestAzurePLSEndpointName(t *testing.T) {
	g := NewGomegaWithT(t)

	testCases := []struct {
		name        string
		meta        metav1.ObjectMeta
		prefix      string
		description string
	}{
		{
			name: "normal_azure_endpoint",
			meta: metav1.ObjectMeta{
				Name:      "endpoint",
				Namespace: "default",
			},
			prefix:      "azure",
			description: "Should generate Azure PLS endpoint name",
		},
		{
			name: "long_azure_endpoint",
			meta: metav1.ObjectMeta{
				Name:      strings.Repeat("verylongname", 10),
				Namespace: strings.Repeat("namespace", 10),
			},
			prefix:      "azure-pls",
			description: "Should handle long Azure PLS endpoint names",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result := AzurePLSEndpointName(tc.meta, tc.prefix)

			g.Expect(result).To(ContainSubstring(tc.prefix+"-"), "Should contain prefix")
			g.Expect(result).To(MatchRegexp(`.*-[a-f0-9]{8}$`), "Should end with hash")
			g.Expect(len(result)).To(BeNumerically("<=", 63), "Should respect RFC 1035 limit")

			t.Logf("Generated Azure PLS endpoint name: %s (length: %d)", result, len(result))
		})
	}
}

func TestDomainNamespace(t *testing.T) {
	g := NewGomegaWithT(t)

	testCases := []struct {
		name           string
		domainName     string
		expectedOutput string
		description    string
	}{
		{
			name:           "full_domain_name",
			domainName:     "service.namespace.svc.cluster.local",
			expectedOutput: "service",
			description:    "Should extract service name from full domain",
		},
		{
			name:           "simple_domain",
			domainName:     "service.domain",
			expectedOutput: "service",
			description:    "Should extract first part of simple domain",
		},
		{
			name:           "no_dots",
			domainName:     "servicename",
			expectedOutput: "servicename",
			description:    "Should return original name when no dots present",
		},
		{
			name:           "empty_domain",
			domainName:     "",
			expectedOutput: "",
			description:    "Should handle empty domain name",
		},
		{
			name:           "domain_starts_with_dot",
			domainName:     ".service.domain",
			expectedOutput: ".service.domain",
			description:    "Should return original when domain starts with dot (index 0 case)",
		},
		{
			name:           "multiple_dots",
			domainName:     "a.b.c.d.e",
			expectedOutput: "a",
			description:    "Should extract first segment from multiple dots",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result := DomainNamespace(tc.domainName)
			g.Expect(result).To(Equal(tc.expectedOutput), tc.description)
		})
	}
}

func TestInterfaceEndpointName(t *testing.T) {
	g := NewGomegaWithT(t)

	testCases := []struct {
		name        string
		meta        metav1.ObjectMeta
		domainName  string
		prefix      string
		description string
		validate    func(string, *GomegaWithT)
	}{
		{
			name: "normal_interface_endpoint",
			meta: metav1.ObjectMeta{
				Name:      "endpoint",
				Namespace: "default",
			},
			domainName:  "service.namespace.svc.cluster.local",
			prefix:      "vpc",
			description: "Should generate interface endpoint name with domain and prefix",
			validate: func(result string, g *GomegaWithT) {
				g.Expect(result).To(ContainSubstring("service-"))
				g.Expect(result).To(ContainSubstring("vpc-"))
				g.Expect(result).To(MatchRegexp(`.*-[a-f0-9]{8}$`))
			},
		},
		{
			name: "domain_without_dots",
			meta: metav1.ObjectMeta{
				Name:      "endpoint",
				Namespace: "default",
			},
			domainName:  "plainservice",
			prefix:      "vpc",
			description: "Should handle domain name without dots",
			validate: func(result string, g *GomegaWithT) {
				g.Expect(result).To(ContainSubstring("plainservice-"))
				g.Expect(result).To(ContainSubstring("vpc-"))
				g.Expect(result).To(MatchRegexp(`.*-[a-f0-9]{8}$`))
			},
		},
		{
			name: "empty_domain",
			meta: metav1.ObjectMeta{
				Name:      "endpoint",
				Namespace: "default",
			},
			domainName:  "",
			prefix:      "vpc",
			description: "Should handle empty domain name",
			validate: func(result string, g *GomegaWithT) {
				g.Expect(result).To(ContainSubstring("vpc-"))
				g.Expect(result).To(MatchRegexp(`.*-[a-f0-9]{8}$`))
			},
		},
		{
			name: "complex_scenario",
			meta: metav1.ObjectMeta{
				Name:      "my-endpoint",
				Namespace: "production",
			},
			domainName:  "api-gateway.backend.svc.cluster.local",
			prefix:      "interface",
			description: "Should handle complex realistic scenario",
			validate: func(result string, g *GomegaWithT) {
				g.Expect(result).To(ContainSubstring("api-gateway-"))
				g.Expect(result).To(ContainSubstring("interface-"))
				g.Expect(result).To(MatchRegexp(`.*-[a-f0-9]{8}$`))
				g.Expect(len(result)).To(BeNumerically("<=", 63))
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result := InterfaceEndpointName(tc.meta, tc.domainName, tc.prefix)
			tc.validate(result, g)
			t.Logf("Generated interface endpoint name: %s (length: %d)", result, len(result))
		})
	}
}

func TestHashConsistency(t *testing.T) {
	g := NewGomegaWithT(t)

	meta := metav1.ObjectMeta{
		Name:      "consistent-test",
		Namespace: "test-namespace",
	}

	// Test that the same metadata always produces the same hash
	results := make([]string, 10)
	for i := 0; i < 10; i++ {
		results[i] = UniqueIdentifier(meta)
	}

	// All results should be identical
	for i := 1; i < len(results); i++ {
		g.Expect(results[i]).To(Equal(results[0]), "Hash should be consistent across multiple calls")
	}

	// Extract and verify hash format
	parts := strings.Split(results[0], "-")
	hash := parts[len(parts)-1]
	g.Expect(len(hash)).To(Equal(hashSize), "Hash should be exactly 8 characters")
	g.Expect(hash).To(MatchRegexp(`^[a-f0-9]{8}$`), "Hash should be lowercase hex")
}

func TestEdgeCases(t *testing.T) {
	g := NewGomegaWithT(t)

	t.Run("empty_metadata", func(t *testing.T) {
		meta := metav1.ObjectMeta{}
		result := UniqueIdentifier(meta)
		g.Expect(result).To(MatchRegexp(`^-[a-f0-9]{8}$`), "Should handle empty metadata gracefully")
	})

	t.Run("metadata_with_special_characters", func(t *testing.T) {
		meta := metav1.ObjectMeta{
			Name:      "test-name_with.special@chars",
			Namespace: "namespace-with_special.chars",
		}
		result := UniqueIdentifier(meta)
		g.Expect(result).To(MatchRegexp(`.*-[a-f0-9]{8}$`), "Should handle special characters in metadata")
		g.Expect(len(result)).To(BeNumerically("<=", 63), "Should respect length limits even with special chars")
	})

	t.Run("unicode_characters", func(t *testing.T) {
		meta := metav1.ObjectMeta{
			Name:      "test-名前",
			Namespace: "测试-namespace",
		}
		result := UniqueIdentifier(meta)
		g.Expect(result).To(MatchRegexp(`.*-[a-f0-9]{8}$`), "Should handle unicode characters")
	})
}
