package cert

import (
	"runtime"
	"strings"
	"time"

	"github.com/0xJacky/Nginx-UI/internal/notification"
	"github.com/0xJacky/Nginx-UI/model"
	"github.com/0xJacky/Nginx-UI/settings"
	"github.com/pkg/errors"
	"github.com/uozi-tech/cosy/logger"
)

func AutoCert() {
	defer func() {
		if err := recover(); err != nil {
			buf := make([]byte, 1024)
			runtime.Stack(buf, false)
			logger.Errorf("%s\n%s", err, buf)
		}
	}()
	logger.Info("AutoCert Worker Started")
	autoCertList := model.GetAutoCertList()
	for _, certModel := range autoCertList {
		autoCert(certModel)
	}
	logger.Info("AutoCert Worker End")
}

func autoCert(certModel *model.Cert) {
	confName := certModel.Filename

	log := NewLogger()
	log.SetCertModel(certModel)
	defer log.Close()

	if len(certModel.Filename) == 0 {
		log.Error(ErrCertModelFilenameEmpty)
		return
	}

	if len(certModel.Domains) == 0 {
		log.Error(errors.New("domains list is empty, " +
			"try to reopen auto-cert for this config:" + confName))
		notification.Error("Renew Certificate Error", confName, nil)
		return
	}

	if certModel.SSLCertificatePath == "" {
		log.Error(errors.New("ssl certificate path is empty, " +
			"try to reopen auto-cert for this config:" + confName))
		notification.Error("Renew Certificate Error", confName, nil)
		return
	}

	certInfo, err := GetCertInfo(certModel.SSLCertificatePath)
	if err != nil {
		// Get certificate info error, ignore this certificate
		log.Error(errors.Wrap(err, "get certificate info error"))
		notification.Error("Renew Certificate Error", strings.Join(certModel.Domains, ", "), nil)
		return
	}

	// Calculate certificate age (days since NotBefore)
	certAge := int(time.Since(certInfo.NotBefore).Hours() / 24)
	// Calculate days until expiration
	daysUntilExpiration := int(time.Until(certInfo.NotAfter).Hours() / 24)
	// Calculate total certificate validity period
	totalValidityDays := int(certInfo.NotAfter.Sub(certInfo.NotBefore).Hours() / 24)

	renewalInterval := settings.CertSettings.GetCertRenewalInterval()

	// For certificates with short validity periods (less than renewal interval),
	// use early renewal logic to prevent expiration
	if totalValidityDays < renewalInterval {
		// Renew when 2/3 of the certificate's lifetime remains
		// This provides a safety buffer for short-lived certificates
		earlyRenewalThreshold := 2 * totalValidityDays / 3
		if daysUntilExpiration > earlyRenewalThreshold {
			return
		}
		// If we reach here, proceed with renewal for short-lived certificate
	} else {
		// For normal certificates with validity >= renewal interval:
		// Skip renewal if certificate age is less than the configured renewal interval
		// This ensures we don't renew certificates too frequently
		if certAge < renewalInterval {
			return
		}
	}

	// after 1 mo, reissue certificate
	// support SAN certification
	payload := &ConfigPayload{
		CertID:                  certModel.ID,
		ServerName:              certModel.Domains,
		ChallengeMethod:         certModel.ChallengeMethod,
		DNSCredentialID:         certModel.DnsCredentialID,
		KeyType:                 certModel.GetKeyType(),
		ACMEUserID:              certModel.ACMEUserID,
		NotBefore:               certInfo.NotBefore,
		MustStaple:              certModel.MustStaple,
		LegoDisableCNAMESupport: certModel.LegoDisableCNAMESupport,
		RevokeOld:               certModel.RevokeOld,
	}

	if certModel.Resource != nil {
		payload.Resource = &model.CertificateResource{
			Resource:          certModel.Resource.Resource,
			PrivateKey:        certModel.Resource.PrivateKey,
			Certificate:       certModel.Resource.Certificate,
			IssuerCertificate: certModel.Resource.IssuerCertificate,
			CSR:               certModel.Resource.CSR,
		}
	}

	err = IssueCert(payload, log)
	if err != nil {
		log.Error(err)
		notification.Error("Renew Certificate Error", strings.Join(payload.ServerName, ", "), nil)
		return
	}

	notification.Success("Renew Certificate Success", strings.Join(payload.ServerName, ", "), nil)
	err = SyncToRemoteServer(certModel)
	if err != nil {
		notification.Error("Sync Certificate Error", err.Error(), nil)
		return
	}
}
