using System;
using System.Security.Cryptography;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using Xunit;

namespace k8s.Tests
{
    public class CertificateValidationTests
    {
        [Fact]
        public void ShouldRejectCertFromDifferentCA()
        {
            // Load our "trusted" Kubernetes CA
            var trustedCaCert = CertUtils.LoadPemFileCert("assets/ca.crt");

            // Generate a completely different CA and server cert in memory
            using (var differentCA = CreateSelfSignedCA("CN=Different CA"))
            using (var untrustedServerCert = CreateServerCert(differentCA, "CN=fake-server.com"))
            {
                var chain = new X509Chain();

                // Pre-populate the chain like SSL validation would do
                // This will likely succeed because we allow unknown CAs in the validation
                chain.Build(untrustedServerCert);

                var errors = SslPolicyErrors.RemoteCertificateChainErrors;

                var result = Kubernetes.CertificateValidationCallBack(this, trustedCaCert, untrustedServerCert, chain, errors);

                // This SHOULD be false because the server cert wasn't signed by our trusted CA
                // But the current K8s validation logic might incorrectly return true
                Assert.False(result, "Should reject certificates not signed by trusted CA");
            }

            // Cleanup
            // differentCA.Dispose();
            // untrustedServerCert.Dispose();
        }

        // Helper methods to create test certificates
        private static X509Certificate2 CreateSelfSignedCA(string subject)
        {
            using (var rsa = RSA.Create(2048))
            {
                var req = new CertificateRequest(subject, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
                req.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true));
                req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign | X509KeyUsageFlags.CrlSign, true));

                return req.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(365));
            }
        }

        private static X509Certificate2 CreateServerCert(X509Certificate2 issuerCA, string subject)
        {
            using (var rsa = RSA.Create(2048))
            {
                var req = new CertificateRequest(subject, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
                req.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
                req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, true));
                req.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, true));

                return req.Create(issuerCA, DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(90), new byte[] { 1, 2, 3, 4 });
            }
        }

        [Fact]
        public void ValidCert()
        {
            var caCert = CertUtils.LoadPemFileCert("assets/ca.crt");
#if NET9_0_OR_GREATER
            var testCert = X509CertificateLoader.LoadCertificateFromFile("assets/ca.crt");
#else
            var testCert = new X509Certificate2("assets/ca.crt");
#endif
            var chain = new X509Chain();
            var errors = SslPolicyErrors.RemoteCertificateChainErrors;

            var result = Kubernetes.CertificateValidationCallBack(this, caCert, testCert, chain, errors);

            Assert.True(result);
        }

        [Fact]
        public void InvalidCert()
        {
            var caCert = CertUtils.LoadPemFileCert("assets/ca.crt");
#if NET9_0_OR_GREATER
            var testCert = X509CertificateLoader.LoadCertificateFromFile("assets/ca2.crt");
#else
            var testCert = new X509Certificate2("assets/ca2.crt");
#endif
            var chain = new X509Chain();
            var errors = SslPolicyErrors.RemoteCertificateChainErrors;

            var result = Kubernetes.CertificateValidationCallBack(this, caCert, testCert, chain, errors);

            Assert.False(result);
        }

        [Fact]
        public void ValidBundleCert()
        {
            var caCert = CertUtils.LoadPemFileCert("assets/ca-bundle.crt");

            // Load the intermediate cert
            //
            var testCert = caCert[0];
            var chain = new X509Chain();
            var errors = SslPolicyErrors.RemoteCertificateChainErrors;

            var result = Kubernetes.CertificateValidationCallBack(this, caCert, testCert, chain, errors);

            Assert.True(result);
        }

        [Fact]
        public void InvalidBundleCert()
        {
            var caCert = CertUtils.LoadPemFileCert("assets/ca-bundle.crt");
#if NET9_0_OR_GREATER
            var testCert = X509CertificateLoader.LoadCertificateFromFile("assets/ca2.crt");
#else
            var testCert = new X509Certificate2("assets/ca2.crt");
#endif
            var chain = new X509Chain();
            var errors = SslPolicyErrors.RemoteCertificateChainErrors;

            var result = Kubernetes.CertificateValidationCallBack(this, caCert, testCert, chain, errors);

            Assert.False(result);
        }

        [Fact]
        public void ValidBundleWithMultipleCerts()
        {
            var caCert = CertUtils.LoadPemFileCert("assets/ca-bundle-correct.crt");

            var testCert = caCert[0];
            var chain = new X509Chain();
            var errors = SslPolicyErrors.RemoteCertificateChainErrors;

            var result = Kubernetes.CertificateValidationCallBack(this, caCert, testCert, chain, errors);

            Assert.True(result);
        }

        [Fact]
        public void InvalidBundleWithMultipleCerts()
        {
            var caCert = CertUtils.LoadPemFileCert("assets/ca-bundle-incorrect.crt");
            var testCert = caCert[0];
            var chain = new X509Chain();
            var errors = SslPolicyErrors.RemoteCertificateChainErrors;

            var result = Kubernetes.CertificateValidationCallBack(this, caCert, testCert, chain, errors);

            Assert.False(result);
        }
    }
}
