// Package tlsutil generates the self-signed certificate used by the direct-TLS SMTP
// listener and builds its tls.Config, mirroring email_server/tls_utils.py.
package tlsutil

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"math/big"
	"os"
	"path/filepath"
	"sync"
	"time"
)

// GenerateSelfSignedCert mirrors tls_utils.generate_self_signed_cert: skips generation
// if both files already exist; otherwise writes an RSA-2048/SHA-256, 1-year-valid,
// self-signed cert with the same subject fields as the Python version.
func GenerateSelfSignedCert(certFile, keyFile string) error {
	if _, err := os.Stat(certFile); err == nil {
		if _, err := os.Stat(keyFile); err == nil {
			return nil
		}
	}
	if err := os.MkdirAll(filepath.Dir(certFile), 0o755); err != nil {
		return err
	}
	if err := os.MkdirAll(filepath.Dir(keyFile), 0o755); err != nil {
		return err
	}

	priv, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return err
	}

	subject := pkix.Name{
		CommonName:   "localhost",
		Organization: []string{"PyMTA Server"},
		Country:      []string{"GB"},
	}
	template := x509.Certificate{
		SerialNumber:          big.NewInt(1000),
		Subject:               subject,
		Issuer:                subject,
		NotBefore:             time.Now(),
		NotAfter:              time.Now().Add(365 * 24 * time.Hour),
		SignatureAlgorithm:    x509.SHA256WithRSA,
		BasicConstraintsValid: true,
	}

	certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
	if err != nil {
		return err
	}

	certOut, err := os.Create(certFile)
	if err != nil {
		return err
	}
	defer certOut.Close()
	if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certDER}); err != nil {
		return err
	}

	keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
	if err != nil {
		return err
	}
	defer keyOut.Close()
	keyDER, err := x509.MarshalPKCS8PrivateKey(priv)
	if err != nil {
		return err
	}
	return pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyDER})
}

// CreateSSLContext mirrors tls_utils.create_ssl_context: loads the cert/key pair and
// pins MinVersion to TLS 1.2 (Python's ssl.create_default_context leaves this to the
// environment's OpenSSL defaults, which is typically TLS 1.2+ on modern systems —
// pinning it explicitly here is the closest deterministic equivalent). Cipher suites
// are left at Go's own secure defaults, matching the Python code's "DEFAULT" relaxation.
func CreateSSLContext(certFile, keyFile string) (*tls.Config, error) {
	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return nil, err
	}
	return &tls.Config{
		Certificates: []tls.Certificate{cert},
		MinVersion:   tls.VersionTLS12,
	}, nil
}

// CertReloader holds the currently-active certificate behind a tls.Config's
// GetCertificate hook, so a listener can pick up a newly-obtained/renewed certificate
// (see internal/acmecert) without restarting the process.
type CertReloader struct {
	certFile, keyFile string

	mu   sync.RWMutex
	cert *tls.Certificate
}

// NewCertReloader loads certFile/keyFile once and returns a reloader ready to hand to
// a tls.Config's GetCertificate field.
func NewCertReloader(certFile, keyFile string) (*CertReloader, error) {
	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return nil, err
	}
	return &CertReloader{certFile: certFile, keyFile: keyFile, cert: &cert}, nil
}

// GetCertificate satisfies tls.Config.GetCertificate.
func (r *CertReloader) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.cert, nil
}

// Reload re-reads certFile/keyFile from disk and atomically swaps the active
// certificate. Called after a successful Let's Encrypt obtain/renew.
func (r *CertReloader) Reload() error {
	cert, err := tls.LoadX509KeyPair(r.certFile, r.keyFile)
	if err != nil {
		return err
	}
	r.mu.Lock()
	r.cert = &cert
	r.mu.Unlock()
	return nil
}

// NewReloadableTLSConfig builds a tls.Config backed by reloader instead of a fixed
// certificate — used by both the SMTP and IMAP implicit-TLS listeners so a single
// Reload() call (self-signed regeneration or a Let's Encrypt renewal) updates both.
func NewReloadableTLSConfig(reloader *CertReloader) *tls.Config {
	return &tls.Config{
		GetCertificate: reloader.GetCertificate,
		MinVersion:     tls.VersionTLS12,
	}
}
