// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================

package settings

import (
	v1 "billionmail-core/api/settings/v1"
	"billionmail-core/internal/consts"
	"billionmail-core/internal/service/public"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"time"
)

var (
	envPath     = public.AbsPath("../.env")
	projectPath = public.AbsPath("../")
)

// loadSSLInfo
func loadSSLInfo() (*v1.SSLConfig, error) {
	certPath := public.AbsPath(filepath.Join(consts.SSL_PATH, "cert.pem"))
	keyPath := public.AbsPath(filepath.Join(consts.SSL_PATH, "key.pem"))

	if _, err := os.Stat(certPath); os.IsNotExist(err) {
		return &v1.SSLConfig{Status: false}, nil
	}
	if _, err := os.Stat(keyPath); os.IsNotExist(err) {
		return &v1.SSLConfig{Status: false}, nil
	}

	certData, err := os.ReadFile(certPath)
	if err != nil {
		return &v1.SSLConfig{Status: false}, nil
	}

	keyData, err := os.ReadFile(keyPath)
	if err != nil {
		return &v1.SSLConfig{Status: false}, nil
	}

	block, _ := pem.Decode(certData)
	if block == nil {
		return &v1.SSLConfig{Status: false}, nil
	}

	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		return &v1.SSLConfig{Status: false}, nil
	}

	// 检查证书是否过期
	now := time.Now()
	if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
		return &v1.SSLConfig{
			CertPath:          certPath,
			KeyPath:           keyPath,
			CertData:          string(certData),
			KeyData:           string(keyData),
			ValidUntil:        cert.NotAfter.Format(time.RFC3339),
			ValidFrom:         cert.NotBefore.Format(time.RFC3339),
			Issuer:            cert.Issuer.String(),
			Subject:           cert.Subject.String(),
			DNSNames:          cert.DNSNames,
			SerialNumber:      cert.SerialNumber.String(),
			IsCA:              cert.IsCA,
			IssuerCommonName:  cert.Issuer.CommonName,
			SubjectCommonName: cert.Subject.CommonName,
			Version:           cert.Version,
			Status:            false,
		}, nil
	}

	// 返回SSL配置
	return &v1.SSLConfig{
		CertPath:          certPath,
		KeyPath:           keyPath,
		CertData:          string(certData),
		KeyData:           string(keyData),
		ValidUntil:        cert.NotAfter.Format(time.RFC3339),
		ValidFrom:         cert.NotBefore.Format(time.RFC3339),
		Issuer:            cert.Issuer.String(),
		Subject:           cert.Subject.String(),
		DNSNames:          cert.DNSNames,
		SerialNumber:      cert.SerialNumber.String(),
		IsCA:              cert.IsCA,
		IssuerCommonName:  cert.Issuer.CommonName,
		SubjectCommonName: cert.Subject.CommonName,
		Version:           cert.Version,
		Status:            true,
	}, nil
}

// convertEnvToConfig Convert environment variables to configuration structure
func convertEnvToConfig(envMap map[string]string) *v1.SystemConfig {
	config := &v1.SystemConfig{}

	// Basic configuration
	config.AdminUsername = envMap["ADMIN_USERNAME"]
	config.AdminPassword = envMap["ADMIN_PASSWORD"]
	config.SafePath = envMap["SafePath"]
	config.Hostname = envMap["BILLIONMAIL_HOSTNAME"]

	// Database configuration
	config.DBName = envMap["DBNAME"]
	config.DBUser = envMap["DBUSER"]
	//config.DBPass = envMap["DBPASS"]

	// Redis configuration
	//config.RedisPass = envMap["REDISPASS"]
	config.RedisPort = envMap["REDIS_PORT"]

	// Mail port configuration
	if port := envMap["SMTP_PORT"]; port != "" {
		config.MailPorts.SMTP = parseInt(port, 25)
	}
	if port := envMap["SMTPS_PORT"]; port != "" {
		config.MailPorts.SMTPS = parseInt(port, 465)
	}
	if port := envMap["SUBMISSION_PORT"]; port != "" {
		config.MailPorts.Submission = parseInt(port, 587)
	}
	if port := envMap["IMAP_PORT"]; port != "" {
		config.MailPorts.IMAP = parseInt(port, 143)
	}
	if port := envMap["IMAPS_PORT"]; port != "" {
		config.MailPorts.IMAPS = parseInt(port, 993)
	}
	if port := envMap["POP_PORT"]; port != "" {
		config.MailPorts.POP = parseInt(port, 110)
	}
	if port := envMap["POPS_PORT"]; port != "" {
		config.MailPorts.POPS = parseInt(port, 995)
	}

	// Management port configuration
	if port := envMap["HTTP_PORT"]; port != "" {
		config.ManagePorts.HTTP = parseInt(port, 80)
	}
	if port := envMap["HTTPS_PORT"]; port != "" {
		config.ManagePorts.HTTPS = parseInt(port, 443)
	}

	HostWorkDir := public.HostWorkDir
	if HostWorkDir == "" {
		HostWorkDir = projectPath
	}
	config.ManagePorts.Command1 = fmt.Sprintf("cd %s && echo \"PORT\" | bash bm.sh change-port", HostWorkDir)
	config.ManagePorts.Command2 = fmt.Sprintf("cd %s && echo \"PORT\" | bash bm.sh change-apply-ssl-port", HostWorkDir)

	// Time zone configuration
	config.ManageTimeZone.TimeZone = envMap["TZ"]
	config.ManageTimeZone.Command = fmt.Sprintf("cd %s && echo \"TIME ZONE\" | bash bm.sh change-tz", HostWorkDir)

	// Network configuration
	config.IPv4Network = envMap["IPV4_NETWORK"]
	config.Fail2ban = envMap["FAIL2BAN_INIT"] == "y"

	// IP whitelist  IP_WHITELIST_ENABLE
	config.IPWhitelistEnabled = envMap["IP_WHITELIST_ENABLE"] == "true"

	// Data retention days
	if retentionDays := envMap["RETENTION_DAYS"]; retentionDays != "" {
		config.RetentionDays = parseInt(retentionDays, 7)
	}

	return config
}

// parseInt Safely convert string to integer
func parseInt(s string, defaultValue int) int {
	if v, err := strconv.Atoi(s); err == nil {
		return v
	}
	return defaultValue
}

// validateConfigValue
func validateConfigValue(key, value string) error {
	// Basic length check
	if len(value) > 1024 {
		return fmt.Errorf("configuration value too long, maximum 1024 characters")
	}

	switch key {
	case "ADMIN_USERNAME", "admin_username":
		// Admin username: allowed letters, numbers, underscores, length 4-32
		if len(value) < 4 || len(value) > 32 {
			return fmt.Errorf("admin username length must be between 4-32")
		}
		if !public.IsValidUsername(value) {
			return fmt.Errorf("admin username can only contain letters, numbers and underscores")
		}

	case "ADMIN_PASSWORD", "admin_password":
		if len(value) < 4 {
			return fmt.Errorf("password length must be at least 4 characters")
		}

	case "BILLIONMAIL_HOSTNAME", "billionmail_hostname":
		// Hostname: allowed letters, numbers, dots, hyphens
		if !public.IsValidHostname(value) {
			return fmt.Errorf("hostname format is incorrect")
		}

	case "SMTP_PORT", "SMTPS_PORT", "SUBMISSION_PORT", "IMAP_PORT", "IMAPS_PORT", "POP_PORT", "POPS_PORT", "HTTP_PORT", "HTTPS_PORT", "REDIS_PORT",
		"smtp", "smtps", "submission", "imap", "imaps", "pop", "pops", "http", "https", "redis_port":
		// Port: 1-65535
		port := public.ParseInt(value)
		if port < 1 || port > 65535 {
			return fmt.Errorf("port must be between 1-65535")
		}

	case "IPV4_NETWORK", "ipv4_network":
		// IPv4 network: CIDR format
		if !public.IsValidCIDR(value) {
			return fmt.Errorf("IPv4 network format is incorrect, please use CIDR format (e.g. 192.168.1.0/24)")
		}

	case "TZ", "timezone":
		// Timezone: check if it is a valid timezone
		if !public.IsValidTimezone(value) {
			return fmt.Errorf("invalid timezone")
		}

	case "FAIL2BAN_INIT", "fail2ban":
		// fail2ban: only allowed y/n or 1/0
		if value != "y" && value != "n" && value != "1" && value != "0" {
			return fmt.Errorf("fail2ban value can only be y/n or 1/0")
		}
	case "RETENTION_DAYS", "retention_days":
		// retention_days: must be a number
		if _, err := strconv.Atoi(value); err != nil {
			return fmt.Errorf("retention_days must be a number")
		}
	}

	// General character check: not allowed dangerous characters
	if public.ContainsDangerousChars(value) {
		return fmt.Errorf("configuration value contains illegal characters")
	}

	return nil
}
