package tls

import (
	"crypto/ecdsa"
	"crypto/ed25519"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"errors"
	"fmt"
	"net"
	"net/url"
	"os"
	"time"
)

var (
	ErrDecodeCAPEM               = errors.New("failed to decode CA certificate PEM")
	ErrInvalidCertificateType    = errors.New("invalid certificate type")
	ErrHostnameRequired          = errors.New("Hostname is required in CertificateOptions")
	ErrNoCertificatesProvided    = errors.New("at least one certificate is required")
	ErrInvalidKeyType            = errors.New("invalid key type")
	ErrUnsupportedPrivateKeyType = errors.New("unsupported private key type")
	ErrFailedParsePrivateKey     = errors.New("failed to parse private key: unsupported key format")
	ErrFailedDecodeCertPEM       = errors.New("failed to decode certificate PEM")
	ErrFailedDecodeKeyPEM        = errors.New("failed to decode private key PEM")
	ErrPrivateKeyNotRSA          = errors.New("private key is not RSA")
)

// KeyType represents the type of cryptographic key to use for certificate generation.
type KeyType string

const (
	KeyTypeRSA     KeyType = "RSA"
	KeyTypeECDSA   KeyType = "ECDSA"
	KeyTypeED25519 KeyType = "ED25519"
)

const (
	certTypeCA     = "CA"
	certTypeServer = "Server"
	certTypeClient = "Client"
)

// CertificateOptions contains optional settings for certificate generation.
// If a field is nil or zero, default values will be used.
type CertificateOptions struct {
	// NotBefore is the certificate validity start time.
	// If zero, defaults to time.Now().
	NotBefore time.Time

	// NotAfter is the certificate validity end time.
	// If zero, defaults will be used based on certificate type.
	NotAfter time.Time

	// DNSNames contains the DNS names for the Subject Alternative Name extension.
	// If nil, default values may be used based on certificate type.
	DNSNames []string

	// IPAddresses contains the IP addresses for the Subject Alternative Name extension.
	// If nil, default values may be used based on certificate type.
	IPAddresses []net.IP

	// EmailAddresses contains the email addresses for the Subject Alternative Name extension.
	// If nil, no email addresses will be included.
	EmailAddresses []string

	// URIs contains the URIs for the Subject Alternative Name extension.
	// If nil, no URIs will be included.
	URIs []string

	// Hostname is the hostname or IP address for server certificates.
	// For server certificates, this is required and will be added to DNSNames or IPAddresses
	// based on whether it's a valid IP address or a DNS name.
	Hostname string

	// CommonName is the CommonName (CN) for certificates.
	// For client certificates, this is optional - if not provided, the certificate will not have a CN.
	CommonName string

	// OrganizationalUnit is the OrganizationalUnit (OU) for certificates.
	// If not provided, the certificate will not have an OU.
	OrganizationalUnit string

	// KeyType specifies the type of cryptographic key to use.
	// Valid values: "RSA" (default), "ECDSA", "ED25519".
	// If empty or "RSA", RSA keys will be generated.
	KeyType KeyType
}

// generateCertificate is a helper function that generates a certificate and private key.
// If signerCert and signerKey are nil, the certificate will be self-signed.
// signerKey can be *rsa.PrivateKey, *ecdsa.PrivateKey, or ed25519.PrivateKey.
func generateCertificate(
	certType string,
	opts *CertificateOptions,
	signerCert *x509.Certificate,
	signerKey any, // Can be *rsa.PrivateKey, *ecdsa.PrivateKey, or ed25519.PrivateKey
) ([]byte, []byte, error) {
	var (
		issuerCert *x509.Certificate
		issuerKey  any
		privKey    any
		publicKey  any
		err        error
	)

	// Determine key type
	keyType := KeyTypeRSA
	if opts != nil && opts.KeyType != "" {
		keyType = opts.KeyType
	}

	// Generate private key based on key type
	switch keyType {
	case KeyTypeRSA:
		rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to generate RSA private key: %w", err)
		}
		privKey = rsaKey
		publicKey = &rsaKey.PublicKey
	case KeyTypeECDSA:
		ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to generate ECDSA private key: %w", err)
		}
		privKey = ecKey
		publicKey = &ecKey.PublicKey
	case KeyTypeED25519:
		edPublicKey, edKey, err := ed25519.GenerateKey(rand.Reader)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to generate ED25519 private key: %w", err)
		}
		privKey = edKey
		publicKey = edPublicKey
	default:
		return nil, nil, fmt.Errorf("%w: %s", ErrInvalidKeyType, keyType)
	}

	// Initialize certificate template
	template, err := initializeTemplate(certType)
	if err != nil {
		return nil, nil, err
	}

	// Apply options
	applyOptions(template, opts, certType)

	// Determine signer (self-signed if signerCert is nil)

	if signerCert == nil {
		// Self-signed
		issuerCert = template
		issuerKey = privKey
	} else {
		// Signed by CA
		issuerCert = signerCert
		issuerKey = signerKey
	}

	// Create the certificate
	certDER, err := x509.CreateCertificate(rand.Reader, template, issuerCert, publicKey, issuerKey)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to create certificate: %w", err)
	}

	// Encode certificate to PEM
	certPEM := pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: certDER,
	})

	// Encode private key to PEM based on key type
	var keyPEM []byte

	switch privKeyType := privKey.(type) {
	case *rsa.PrivateKey:
		keyPEM = pem.EncodeToMemory(&pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: x509.MarshalPKCS1PrivateKey(privKeyType),
		})
	case *ecdsa.PrivateKey:
		keyBytes, err := x509.MarshalECPrivateKey(privKeyType)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to marshal ECDSA private key: %w", err)
		}
		keyPEM = pem.EncodeToMemory(&pem.Block{
			Type:  "EC PRIVATE KEY",
			Bytes: keyBytes,
		})
	case ed25519.PrivateKey:
		keyBytes, err := x509.MarshalPKCS8PrivateKey(privKeyType)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to marshal ED25519 private key: %w", err)
		}
		keyPEM = pem.EncodeToMemory(&pem.Block{
			Type:  "PRIVATE KEY",
			Bytes: keyBytes,
		})
	default:
		return nil, nil, fmt.Errorf("%w: %T", ErrUnsupportedPrivateKeyType, privKey)
	}

	return certPEM, keyPEM, nil
}

// parsePrivateKeyFromPEM parses a private key from PEM-encoded bytes.
// Tries PKCS8 first (handles RSA, ECDSA, and ED25519), then falls back to PKCS1 (RSA) and EC SEC1 (ECDSA).
func parsePrivateKeyFromPEM(keyBytes []byte) (any, error) {
	// Try PKCS8 first (handles RSA, ECDSA, and ED25519)
	if privKey, err := x509.ParsePKCS8PrivateKey(keyBytes); err == nil {
		return privKey, nil
	}

	// Fall back to PKCS1 (RSA only)
	if rsaKey, err := x509.ParsePKCS1PrivateKey(keyBytes); err == nil {
		return rsaKey, nil
	}

	// Fall back to EC SEC1 format
	if ecKey, err := x509.ParseECPrivateKey(keyBytes); err == nil {
		return ecKey, nil
	}

	return nil, ErrFailedParsePrivateKey
}

// ExtractPublicKeyFromCert extracts the public key from a certificate in PEM format.
// Returns the public key in PKIX format (suitable for ECDSA and ED25519).
func ExtractPublicKeyFromCert(certPEM []byte) ([]byte, error) {
	block, _ := pem.Decode(certPEM)
	if block == nil {
		return nil, ErrFailedDecodeCertPEM
	}

	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("failed to parse certificate: %w", err)
	}

	publicKeyBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal public key: %w", err)
	}

	return pem.EncodeToMemory(&pem.Block{
		Type:  "PUBLIC KEY",
		Bytes: publicKeyBytes,
	}), nil
}

// ExtractRSAPublicKeyPKCS1 extracts the RSA public key from a private key in PEM format.
// Returns the public key in PKCS1 format (RSA-specific).
func ExtractRSAPublicKeyPKCS1(keyPEM []byte) ([]byte, error) {
	block, _ := pem.Decode(keyPEM)
	if block == nil {
		return nil, ErrFailedDecodeKeyPEM
	}

	privKey, err := parsePrivateKeyFromPEM(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("failed to parse RSA private key: %w", err)
	}

	rsaKey, ok := privKey.(*rsa.PrivateKey)
	if !ok {
		return nil, fmt.Errorf("%w, got %T", ErrPrivateKeyNotRSA, privKey)
	}

	publicKeyBytes := x509.MarshalPKCS1PublicKey(&rsaKey.PublicKey)

	return pem.EncodeToMemory(&pem.Block{
		Type:  "RSA PUBLIC KEY",
		Bytes: publicKeyBytes,
	}), nil
}

// parseCA parses CA certificate and private key from PEM format.
// Returns the certificate and the private key (which can be *rsa.PrivateKey, *ecdsa.PrivateKey, or ed25519.PrivateKey).
func parseCA(caCertPEM, caKeyPEM []byte) (*x509.Certificate, any, error) {
	// Parse CA certificate
	caCertBlock, _ := pem.Decode(caCertPEM)
	if caCertBlock == nil {
		return nil, nil, ErrDecodeCAPEM
	}

	caCert, err := x509.ParseCertificate(caCertBlock.Bytes)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to parse CA certificate: %w", err)
	}

	// Parse CA private key
	caKeyBlock, _ := pem.Decode(caKeyPEM)
	if caKeyBlock == nil {
		return nil, nil, ErrDecodeCAPEM
	}

	caPrivKey, err := parsePrivateKeyFromPEM(caKeyBlock.Bytes)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to parse CA private key: %w", err)
	}

	return caCert, caPrivKey, nil
}

// initializeTemplate creates and initializes a certificate template based on the certificate type.
// certType can be "CA", "Server", or "Client".
func initializeTemplate(certType string) (*x509.Certificate, error) {
	template := &x509.Certificate{}

	// Initialize certificate type-specific fields and defaults
	switch certType {
	case certTypeCA:
		template.IsCA = true
		template.ExtKeyUsage = []x509.ExtKeyUsage{}
		template.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign
		template.BasicConstraintsValid = true
		template.Subject = pkix.Name{
			Organization:  []string{"Test CA"},
			Country:       []string{"US"},
			Province:      []string{""},
			Locality:      []string{"San Francisco"},
			StreetAddress: []string{""},
			PostalCode:    []string{""},
		}
		// NotBefore and NotAfter are set via CertificateOptions in test logic
	case certTypeServer:
		template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
		template.KeyUsage = x509.KeyUsageDigitalSignature
		template.Subject = pkix.Name{
			Organization:  []string{"Test Server"},
			Country:       []string{"US"},
			Province:      []string{""},
			Locality:      []string{"San Francisco"},
			StreetAddress: []string{""},
			PostalCode:    []string{""},
		}
		// NotBefore and NotAfter are set via CertificateOptions in test logic
		template.IPAddresses = []net.IP{net.ParseIP("127.0.0.1")} // Default IP for Server
	case certTypeClient:
		template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
		template.KeyUsage = x509.KeyUsageDigitalSignature
		template.Subject = pkix.Name{
			Organization:  []string{"Test Client"},
			Country:       []string{"US"},
			Province:      []string{""},
			Locality:      []string{"San Francisco"},
			StreetAddress: []string{""},
			PostalCode:    []string{""},
		}
		// NotBefore and NotAfter are set via CertificateOptions in test logic
	default:
		return nil, fmt.Errorf("%w: %s", ErrInvalidCertificateType, certType)
	}

	return template, nil
}

// applyOptions applies options to the certificate template, using defaults when options are not provided.
// certType can be "CA", "Server", or "Client".
func applyOptions(template *x509.Certificate, opts *CertificateOptions, certType string) {
	if opts == nil {
		opts = &CertificateOptions{}
	}

	// Apply NotBefore - default to time.Now() if not provided
	if !opts.NotBefore.IsZero() {
		template.NotBefore = opts.NotBefore
	} else {
		template.NotBefore = time.Now()
	}

	// Apply NotAfter - default to 1 year if not provided, matching gen_certs.sh
	if !opts.NotAfter.IsZero() {
		template.NotAfter = opts.NotAfter
	} else {
		template.NotAfter = time.Now().AddDate(1, 0, 0)
	}

	// Apply SAN (Subject Alternative Name) - handle IPAddresses
	// Priority: 1) opts.IPAddresses, 2) hostname if IP, 3) keep default from initializeTemplate
	if opts.IPAddresses != nil {
		template.IPAddresses = opts.IPAddresses
	} else if certType == certTypeServer && opts.Hostname != "" {
		if ip := net.ParseIP(opts.Hostname); ip != nil {
			// Hostname is an IP address, use it
			template.IPAddresses = []net.IP{ip}
		}
		// If hostname is DNS name, keep default IP from initializeTemplate
	}

	// Apply SAN (Subject Alternative Name) - handle DNSNames
	// Priority: 1) opts.DNSNames, 2) hostname if DNS name
	if opts.DNSNames != nil {
		template.DNSNames = opts.DNSNames
	} else if certType == certTypeServer && opts.Hostname != "" {
		if ip := net.ParseIP(opts.Hostname); ip == nil {
			// Hostname is a DNS name, use it
			template.DNSNames = []string{opts.Hostname}
		}
	}

	// Apply email addresses
	if opts.EmailAddresses != nil {
		template.EmailAddresses = opts.EmailAddresses
	}

	// Apply URIs
	if opts.URIs != nil {
		templateURIs := make([]*url.URL, 0, len(opts.URIs))
		for _, uriStr := range opts.URIs {
			uri, err := url.Parse(uriStr)
			if err != nil {
				// Skip invalid URIs - could log error in production
				continue
			}

			// Validate that the URI has a valid scheme (url.Parse accepts URIs without schemes)
			if uri.Scheme == "" {
				// Skip URIs without a scheme - could log error in production
				continue
			}

			templateURIs = append(templateURIs, uri)
		}
		template.URIs = templateURIs
	}

	// Apply CommonName - if provided, override the default; otherwise keep default from initializeTemplate
	if opts.CommonName != "" {
		template.Subject.CommonName = opts.CommonName
	} else if opts != nil && opts.CommonName == "" && certType == certTypeClient {
		// Special case: For client certs, if opts is provided and CommonName is explicitly set to empty,
		// use empty CN (for noidentity-style certs)
		template.Subject.CommonName = ""
	}

	// Apply OrganizationalUnit - if provided, set it
	if opts.OrganizationalUnit != "" {
		template.Subject.OrganizationalUnit = []string{opts.OrganizationalUnit}
	}
}

// GenerateCACert generates a CA certificate and private key.
// opts is optional and can be used to customize certificate settings.
func GenerateCACert(opts ...*CertificateOptions) ([]byte, []byte, error) {
	var options *CertificateOptions
	if len(opts) > 0 && opts[0] != nil {
		options = opts[0]
	}

	// Self-signed certificate (signerCert and signerKey are nil)
	return generateCertificate(certTypeCA, options, nil, nil)
}

// GenerateIntermediateCACert generates an intermediate CA certificate signed by the provided parent CA.
// opts is optional and can be used to customize certificate settings, including CommonName.
func GenerateIntermediateCACert(
	parentCACertPEM,
	parentCAKeyPEM []byte,
	opts ...*CertificateOptions,
) ([]byte, []byte, error) {
	var options *CertificateOptions
	if len(opts) > 0 && opts[0] != nil {
		options = opts[0]
	} else {
		options = &CertificateOptions{}
	}

	// Parse parent CA certificate and key
	parentCACert, parentCAPrivKey, err := parseCA(parentCACertPEM, parentCAKeyPEM)
	if err != nil {
		return nil, nil, err
	}

	// Generate intermediate CA certificate signed by parent CA
	return generateCertificate(certTypeCA, options, parentCACert, parentCAPrivKey)
}

// writeCertAndKeyToFile writes certificate and key bytes to their respective files.
func writeCertAndKeyToFile(certPath, keyPath string, certBytes, keyBytes []byte) error {
	err := os.WriteFile(certPath, certBytes, 0o600)
	if err != nil {
		return err
	}

	return os.WriteFile(keyPath, keyBytes, 0o600)
}

// WriteCertificateChainToFile writes a certificate chain to a file.
// The certificates should be provided in order: leaf certificate first, followed by intermediate CAs.
// All certificates should be in PEM format.
func WriteCertificateChainToFile(certChainPath string, certs ...[]byte) error {
	if len(certs) == 0 {
		return ErrNoCertificatesProvided
	}

	// Calculate total size for pre-allocation
	totalSize := 0
	for _, cert := range certs {
		totalSize += len(cert)
	}

	// Concatenate all certificates
	chainPEM := make([]byte, 0, totalSize)
	for _, cert := range certs {
		chainPEM = append(chainPEM, cert...)
	}

	// Write to file
	err := os.WriteFile(certChainPath, chainPEM, 0o600)
	if err != nil {
		return err
	}

	return nil
}

// GenerateServerCert generates a server certificate signed by the provided CA.
// opts is required and must contain a Hostname field.
func GenerateServerCert(caCertPEM, caKeyPEM []byte, opts *CertificateOptions) ([]byte, []byte, error) {
	if opts == nil || opts.Hostname == "" {
		return nil, nil, ErrHostnameRequired
	}

	// Parse CA certificate and key
	caCert, caPrivKey, err := parseCA(caCertPEM, caKeyPEM)
	if err != nil {
		return nil, nil, err
	}

	// Generate certificate signed by CA
	return generateCertificate(certTypeServer, opts, caCert, caPrivKey)
}

// GenerateServerCertToFile generates a server certificate signed by the provided CA
// and writes generated key and cert to files.
// opts is required and must contain a Hostname field.
func GenerateServerCertToFile(
	caCertPEM, caKeyPEM []byte,
	certOutputPath, keyOutputPath string,
	opts *CertificateOptions,
) error {
	serverCertBytes, serverKeyBytes, err := GenerateServerCert(caCertPEM, caKeyPEM, opts)
	if err != nil {
		return err
	}

	return writeCertAndKeyToFile(certOutputPath, keyOutputPath, serverCertBytes, serverKeyBytes)
}

// GenerateClientCert generates a client certificate signed by the provided CA.
// opts is optional. CommonName is optional - if not provided, the certificate will not have a CN.
func GenerateClientCert(caCertPEM, caKeyPEM []byte, opts *CertificateOptions) ([]byte, []byte, error) {
	// Parse CA certificate and key
	caCert, caPrivKey, err := parseCA(caCertPEM, caKeyPEM)
	if err != nil {
		return nil, nil, err
	}

	// Generate certificate signed by CA
	return generateCertificate(certTypeClient, opts, caCert, caPrivKey)
}

// GenerateClientCertToFile generates a client certificate signed by the provided CA
// and writes generated key and cert to files.
// opts is optional. CommonName is optional - if not provided, the certificate will not have a CN.
func GenerateClientCertToFile(caCertPEM, caKeyPEM []byte, certPath, keyPath string, opts *CertificateOptions) error {
	clientCertBytes, clientKeyBytes, err := GenerateClientCert(caCertPEM, caKeyPEM, opts)
	if err != nil {
		return err
	}

	return writeCertAndKeyToFile(certPath, keyPath, clientCertBytes, clientKeyBytes)
}

// GenerateClientSelfSignedCert generates a client certificate not signed by any CA.
// opts is optional. CommonName is optional - if not provided, the certificate will not have a CN.
func GenerateClientSelfSignedCert(opts *CertificateOptions) ([]byte, []byte, error) {
	// Self-signed certificate (signerCert and signerKey are nil)
	return generateCertificate(certTypeClient, opts, nil, nil)
}

// GenerateClientSelfSignedCertToFile generates a client certificate not signed by any CA
// and writes generated key and cert to files.
// opts is optional. CommonName is optional - if not provided, the certificate will not have a CN.
func GenerateClientSelfSignedCertToFile(certOutputPath, keyOutputPath string, opts *CertificateOptions) error {
	clientCertBytes, clientKeyBytes, err := GenerateClientSelfSignedCert(opts)
	if err != nil {
		return err
	}

	return writeCertAndKeyToFile(certOutputPath, keyOutputPath, clientCertBytes, clientKeyBytes)
}
