package services

import (
	"crypto/ecdsa"
	"crypto/ed25519"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"math/big"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// --- helpers ---

func makeRSACertAndKey(t *testing.T, cn string, expiry time.Time) (*x509.Certificate, *rsa.PrivateKey, []byte, []byte) {
	t.Helper()
	priv, err := rsa.GenerateKey(rand.Reader, 2048)
	require.NoError(t, err)

	tmpl := &x509.Certificate{
		SerialNumber: big.NewInt(1),
		Subject:      pkix.Name{CommonName: cn},
		NotBefore:    time.Now(),
		NotAfter:     expiry,
		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
	}
	der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &priv.PublicKey, priv)
	require.NoError(t, err)
	cert, err := x509.ParseCertificate(der)
	require.NoError(t, err)

	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
	return cert, priv, certPEM, keyPEM
}

func makeECDSACertAndKey(t *testing.T, cn string) (*x509.Certificate, *ecdsa.PrivateKey, []byte, []byte) {
	t.Helper()
	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	require.NoError(t, err)

	tmpl := &x509.Certificate{
		SerialNumber: big.NewInt(2),
		Subject:      pkix.Name{CommonName: cn},
		NotBefore:    time.Now(),
		NotAfter:     time.Now().Add(24 * time.Hour),
		KeyUsage:     x509.KeyUsageDigitalSignature,
	}
	der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &priv.PublicKey, priv)
	require.NoError(t, err)
	cert, err := x509.ParseCertificate(der)
	require.NoError(t, err)

	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
	keyDER, err := x509.MarshalECPrivateKey(priv)
	require.NoError(t, err)
	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER})
	return cert, priv, certPEM, keyPEM
}

func makeEd25519CertAndKey(t *testing.T, cn string) (*x509.Certificate, ed25519.PrivateKey, []byte, []byte) {
	t.Helper()
	pub, priv, err := ed25519.GenerateKey(rand.Reader)
	require.NoError(t, err)

	tmpl := &x509.Certificate{
		SerialNumber: big.NewInt(3),
		Subject:      pkix.Name{CommonName: cn},
		NotBefore:    time.Now(),
		NotAfter:     time.Now().Add(24 * time.Hour),
		KeyUsage:     x509.KeyUsageDigitalSignature,
	}
	der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv)
	require.NoError(t, err)
	cert, err := x509.ParseCertificate(der)
	require.NoError(t, err)

	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
	keyDER, err := x509.MarshalPKCS8PrivateKey(priv)
	require.NoError(t, err)
	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER})
	return cert, priv, certPEM, keyPEM
}

// --- DetectFormat ---

func TestDetectFormat(t *testing.T) {
	cert, _, certPEM, _ := makeRSACertAndKey(t, "test.com", time.Now().Add(time.Hour))

	t.Run("PEM format", func(t *testing.T) {
		assert.Equal(t, FormatPEM, DetectFormat(certPEM))
	})

	t.Run("DER format", func(t *testing.T) {
		assert.Equal(t, FormatDER, DetectFormat(cert.Raw))
	})

	t.Run("unknown format", func(t *testing.T) {
		assert.Equal(t, FormatUnknown, DetectFormat([]byte("not a cert")))
	})

	t.Run("empty data", func(t *testing.T) {
		assert.Equal(t, FormatUnknown, DetectFormat([]byte{}))
	})
}

// --- ParseCertificateInput ---

func TestParseCertificateInput(t *testing.T) {
	t.Run("PEM cert only", func(t *testing.T) {
		_, _, certPEM, _ := makeRSACertAndKey(t, "pem.test", time.Now().Add(time.Hour))
		parsed, err := ParseCertificateInput(certPEM, nil, nil, "")
		require.NoError(t, err)
		assert.NotNil(t, parsed.Leaf)
		assert.Equal(t, FormatPEM, parsed.Format)
		assert.Nil(t, parsed.PrivateKey)
	})

	t.Run("PEM cert with key", func(t *testing.T) {
		_, _, certPEM, keyPEM := makeRSACertAndKey(t, "pem-key.test", time.Now().Add(time.Hour))
		parsed, err := ParseCertificateInput(certPEM, keyPEM, nil, "")
		require.NoError(t, err)
		assert.NotNil(t, parsed.Leaf)
		assert.NotNil(t, parsed.PrivateKey)
		assert.Equal(t, FormatPEM, parsed.Format)
	})

	t.Run("DER cert", func(t *testing.T) {
		cert, _, _, _ := makeRSACertAndKey(t, "der.test", time.Now().Add(time.Hour))
		parsed, err := ParseCertificateInput(cert.Raw, nil, nil, "")
		require.NoError(t, err)
		assert.NotNil(t, parsed.Leaf)
		assert.Equal(t, FormatDER, parsed.Format)
	})

	t.Run("empty data returns error", func(t *testing.T) {
		_, err := ParseCertificateInput(nil, nil, nil, "")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "empty")
	})

	t.Run("unrecognized format returns error", func(t *testing.T) {
		_, err := ParseCertificateInput([]byte("garbage"), nil, nil, "")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "unrecognized")
	})

	t.Run("invalid key PEM returns error", func(t *testing.T) {
		_, _, certPEM, _ := makeRSACertAndKey(t, "badkey.test", time.Now().Add(time.Hour))
		_, err := ParseCertificateInput(certPEM, []byte("not-key"), nil, "")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "private key")
	})
}

// --- ValidateKeyMatch ---

func TestValidateKeyMatch(t *testing.T) {
	t.Run("RSA matching", func(t *testing.T) {
		cert, priv, _, _ := makeRSACertAndKey(t, "rsa.test", time.Now().Add(time.Hour))
		assert.NoError(t, ValidateKeyMatch(cert, priv))
	})

	t.Run("RSA mismatched", func(t *testing.T) {
		cert, _, _, _ := makeRSACertAndKey(t, "rsa1.test", time.Now().Add(time.Hour))
		_, otherPriv, _, _ := makeRSACertAndKey(t, "rsa2.test", time.Now().Add(time.Hour))
		err := ValidateKeyMatch(cert, otherPriv)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "mismatch")
	})

	t.Run("ECDSA matching", func(t *testing.T) {
		cert, priv, _, _ := makeECDSACertAndKey(t, "ecdsa.test")
		assert.NoError(t, ValidateKeyMatch(cert, priv))
	})

	t.Run("ECDSA mismatched", func(t *testing.T) {
		cert, _, _, _ := makeECDSACertAndKey(t, "ec1.test")
		_, other, _, _ := makeECDSACertAndKey(t, "ec2.test")
		assert.Error(t, ValidateKeyMatch(cert, other))
	})

	t.Run("Ed25519 matching", func(t *testing.T) {
		cert, priv, _, _ := makeEd25519CertAndKey(t, "ed.test")
		assert.NoError(t, ValidateKeyMatch(cert, priv))
	})

	t.Run("Ed25519 mismatched", func(t *testing.T) {
		cert, _, _, _ := makeEd25519CertAndKey(t, "ed1.test")
		_, other, _, _ := makeEd25519CertAndKey(t, "ed2.test")
		assert.Error(t, ValidateKeyMatch(cert, other))
	})

	t.Run("type mismatch RSA cert with ECDSA key", func(t *testing.T) {
		cert, _, _, _ := makeRSACertAndKey(t, "rsa.test", time.Now().Add(time.Hour))
		_, ecKey, _, _ := makeECDSACertAndKey(t, "ec.test")
		err := ValidateKeyMatch(cert, ecKey)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "type mismatch")
	})

	t.Run("nil certificate", func(t *testing.T) {
		_, priv, _, _ := makeRSACertAndKey(t, "rsa.test", time.Now().Add(time.Hour))
		assert.Error(t, ValidateKeyMatch(nil, priv))
	})

	t.Run("nil key", func(t *testing.T) {
		cert, _, _, _ := makeRSACertAndKey(t, "rsa.test", time.Now().Add(time.Hour))
		assert.Error(t, ValidateKeyMatch(cert, nil))
	})
}

// --- ValidateChain ---

func TestValidateChain(t *testing.T) {
	t.Run("nil leaf returns error", func(t *testing.T) {
		err := ValidateChain(nil, nil)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "nil")
	})

	t.Run("self-signed cert validates", func(t *testing.T) {
		cert, _, _, _ := makeRSACertAndKey(t, "self.test", time.Now().Add(time.Hour))
		// Self-signed won't pass chain validation without being a CA
		err := ValidateChain(cert, nil)
		assert.Error(t, err)
	})
}

// --- ConvertDERToPEM ---

func TestConvertDERToPEM(t *testing.T) {
	t.Run("valid DER", func(t *testing.T) {
		cert, _, _, _ := makeRSACertAndKey(t, "der.test", time.Now().Add(time.Hour))
		pemStr, err := ConvertDERToPEM(cert.Raw)
		require.NoError(t, err)
		assert.Contains(t, pemStr, "BEGIN CERTIFICATE")
	})

	t.Run("invalid DER", func(t *testing.T) {
		_, err := ConvertDERToPEM([]byte("not-der"))
		assert.Error(t, err)
	})
}

// --- ConvertPEMToDER ---

func TestConvertPEMToDER(t *testing.T) {
	t.Run("valid PEM", func(t *testing.T) {
		_, _, certPEM, _ := makeRSACertAndKey(t, "p2d.test", time.Now().Add(time.Hour))
		der, err := ConvertPEMToDER(string(certPEM))
		require.NoError(t, err)
		assert.NotEmpty(t, der)
		// Round-trip
		cert, err := x509.ParseCertificate(der)
		require.NoError(t, err)
		assert.Equal(t, "p2d.test", cert.Subject.CommonName)
	})

	t.Run("invalid PEM", func(t *testing.T) {
		_, err := ConvertPEMToDER("not-pem")
		assert.Error(t, err)
	})
}

// --- ExtractCertificateMetadata ---

func TestExtractCertificateMetadata(t *testing.T) {
	t.Run("nil cert returns nil", func(t *testing.T) {
		assert.Nil(t, ExtractCertificateMetadata(nil))
	})

	t.Run("RSA cert metadata", func(t *testing.T) {
		cert, _, _, _ := makeRSACertAndKey(t, "meta.test", time.Now().Add(time.Hour))
		m := ExtractCertificateMetadata(cert)
		require.NotNil(t, m)
		assert.Equal(t, "meta.test", m.CommonName)
		assert.Contains(t, m.KeyType, "RSA")
		assert.NotEmpty(t, m.Fingerprint)
		assert.NotEmpty(t, m.SerialNumber)
		assert.Contains(t, m.Domains, "meta.test")
	})

	t.Run("ECDSA cert metadata", func(t *testing.T) {
		cert, _, _, _ := makeECDSACertAndKey(t, "ec-meta.test")
		m := ExtractCertificateMetadata(cert)
		require.NotNil(t, m)
		assert.Contains(t, m.KeyType, "ECDSA")
	})

	t.Run("Ed25519 cert metadata", func(t *testing.T) {
		cert, _, _, _ := makeEd25519CertAndKey(t, "ed-meta.test")
		m := ExtractCertificateMetadata(cert)
		require.NotNil(t, m)
		assert.Equal(t, "Ed25519", m.KeyType)
	})

	t.Run("cert with SANs", func(t *testing.T) {
		priv, err := rsa.GenerateKey(rand.Reader, 2048)
		require.NoError(t, err)
		tmpl := &x509.Certificate{
			SerialNumber: big.NewInt(10),
			Subject:      pkix.Name{CommonName: "main.test"},
			DNSNames:     []string{"main.test", "alt1.test", "alt2.test"},
			NotBefore:    time.Now(),
			NotAfter:     time.Now().Add(time.Hour),
		}
		der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &priv.PublicKey, priv)
		require.NoError(t, err)
		cert, _ := x509.ParseCertificate(der)

		m := ExtractCertificateMetadata(cert)
		require.NotNil(t, m)
		assert.Contains(t, m.Domains, "main.test")
		assert.Contains(t, m.Domains, "alt1.test")
		assert.Contains(t, m.Domains, "alt2.test")
		// CN should not be duplicated when it matches a SAN
		count := 0
		for _, d := range m.Domains {
			if d == "main.test" {
				count++
			}
		}
		assert.Equal(t, 1, count, "CN should not be duplicated in domains list")
	})

	t.Run("cert with issuer org", func(t *testing.T) {
		priv, err := rsa.GenerateKey(rand.Reader, 2048)
		require.NoError(t, err)
		tmpl := &x509.Certificate{
			SerialNumber: big.NewInt(11),
			Subject:      pkix.Name{CommonName: "org.test"},
			Issuer:       pkix.Name{Organization: []string{"Test Org Inc"}},
			NotBefore:    time.Now(),
			NotAfter:     time.Now().Add(time.Hour),
		}
		der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &priv.PublicKey, priv)
		require.NoError(t, err)
		cert, _ := x509.ParseCertificate(der)

		m := ExtractCertificateMetadata(cert)
		require.NotNil(t, m)
		// Self-signed cert's issuer org may differ from template
		assert.NotEmpty(t, m.Fingerprint)
	})
}

// --- Helpers ---

func TestFormatFingerprint(t *testing.T) {
	assert.Equal(t, "AB:CD:EF", formatFingerprint("abcdef"))
	assert.Equal(t, "01:23", formatFingerprint("0123"))
	assert.Equal(t, "", formatFingerprint(""))
}

func TestFormatSerial(t *testing.T) {
	assert.Equal(t, "01", formatSerial(big.NewInt(1)))
	assert.Equal(t, "FF", formatSerial(big.NewInt(255)))
	assert.Equal(t, "", formatSerial(nil))
}

func TestDetectKeyType(t *testing.T) {
	t.Run("RSA key type", func(t *testing.T) {
		cert, _, _, _ := makeRSACertAndKey(t, "rsa.test", time.Now().Add(time.Hour))
		kt := detectKeyType(cert)
		assert.Contains(t, kt, "RSA-2048")
	})

	t.Run("ECDSA-P256 key type", func(t *testing.T) {
		cert, _, _, _ := makeECDSACertAndKey(t, "ec.test")
		kt := detectKeyType(cert)
		assert.Equal(t, "ECDSA-P256", kt)
	})

	t.Run("Ed25519 key type", func(t *testing.T) {
		cert, _, _, _ := makeEd25519CertAndKey(t, "ed.test")
		kt := detectKeyType(cert)
		assert.Equal(t, "Ed25519", kt)
	})
}
