// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0

package cert

import (
	"crypto/x509"
	"fmt"
	"net"
	"net/url"
	"sort"
	"strings"

	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
)

type CertificateSANs struct {
	DNSNames []string
	IPAddrs  []net.IP
}

func (s CertificateSANs) Empty() bool {
	return len(s.DNSNames) == 0 && len(s.IPAddrs) == 0
}

func (s CertificateSANs) Normalize() CertificateSANs {
	dnsSet := make(map[string]struct{}, len(s.DNSNames))
	dnsNames := make([]string, 0, len(s.DNSNames))

	for _, name := range s.DNSNames {
		name = strings.TrimSpace(strings.ToLower(name))
		if name == "" {
			continue
		}

		if _, ok := dnsSet[name]; ok {
			continue
		}

		dnsSet[name] = struct{}{}

		dnsNames = append(dnsNames, name)
	}

	ipSet := make(map[string]struct{}, len(s.IPAddrs))
	ipAddrs := make([]net.IP, 0, len(s.IPAddrs))

	for _, ip := range s.IPAddrs {
		if ip == nil {
			continue
		}

		normalized := ip.String()
		if _, ok := ipSet[normalized]; ok {
			continue
		}

		ipSet[normalized] = struct{}{}

		ipAddrs = append(ipAddrs, ip)
	}

	sort.Strings(dnsNames)
	sort.Slice(ipAddrs, func(i, j int) bool {
		return ipAddrs[i].String() < ipAddrs[j].String()
	})

	return CertificateSANs{
		DNSNames: dnsNames,
		IPAddrs:  ipAddrs,
	}
}

func (s *CertificateSANs) AddDNSNames(names ...string) {
	s.DNSNames = append(s.DNSNames, names...)
}

func (s *CertificateSANs) AddIPAddrs(ips ...net.IP) {
	s.IPAddrs = append(s.IPAddrs, ips...)
}

func (s *CertificateSANs) AddServiceReference(
	service *admissionregistrationv1.ServiceReference,
	defaultNamespace string,
) {
	if service == nil || service.Name == "" {
		return
	}

	namespace := service.Namespace
	if namespace == "" {
		namespace = defaultNamespace
	}

	s.AddDNSNames(
		service.Name,
		fmt.Sprintf("%s.%s", service.Name, namespace),
		fmt.Sprintf("%s.%s.svc", service.Name, namespace),
		fmt.Sprintf("%s.%s.svc.cluster.local", service.Name, namespace),
	)
}

func (s *CertificateSANs) AddURL(rawURL *string) error {
	if rawURL == nil || strings.TrimSpace(*rawURL) == "" {
		return nil
	}

	parsed, err := url.Parse(*rawURL)
	if err != nil {
		return fmt.Errorf("parse webhook URL %q: %w", *rawURL, err)
	}

	host := parsed.Hostname()
	if host == "" {
		return fmt.Errorf("webhook URL %q has empty host", *rawURL)
	}

	if ip := net.ParseIP(host); ip != nil {
		s.AddIPAddrs(ip)

		return nil
	}

	s.AddDNSNames(host)

	return nil
}

func (s CertificateSANs) MatchesCertificate(certificate *x509.Certificate) bool {
	if certificate == nil {
		return false
	}

	desired := s.Normalize()
	actual := CertificateSANs{
		DNSNames: append([]string(nil), certificate.DNSNames...),
		IPAddrs:  append([]net.IP(nil), certificate.IPAddresses...),
	}.Normalize()

	if len(desired.DNSNames) != len(actual.DNSNames) {
		return false
	}

	for i := range desired.DNSNames {
		if desired.DNSNames[i] != actual.DNSNames[i] {
			return false
		}
	}

	if len(desired.IPAddrs) != len(actual.IPAddrs) {
		return false
	}

	for i := range desired.IPAddrs {
		if desired.IPAddrs[i].String() != actual.IPAddrs[i].String() {
			return false
		}
	}

	return true
}
