package config

import (
	"fmt"
	"net"
	"os"
	"strconv"
	"strings"
	"time"
)

type Config struct {
	Listen        string
	PublicBaseURL string

	SMTP     SMTPConfig
	Nym      NymConfig
	Security SecurityConfig
}

type SMTPConfig struct {
	Host          string
	Port          int
	Recipient     string
	EnvelopeFrom  string
	HELO          string
	TLSServerName string
	RequireTLS    bool
	ImplicitTLS   bool
	Timeout       time.Duration
	DryRun        bool
}

type NymConfig struct {
	Enabled          bool
	Managed          bool
	Binary           string
	Provider         string
	HomeDir          string
	ClientID         string
	SocksAddr        string
	AnonymousReplies bool
	StartupTimeout   time.Duration
}

type SecurityConfig struct {
	MinHashcashBits int
	MinMessageBytes int
	MaxMessageBytes int
	MaxNewsgroups   int
	RateLimitCount  int
	RateLimitWindow time.Duration
	TrustProxy      bool
	SecureCookies   bool
	MessageIDDomain string
	IdenticonsCLI   string
	RequireFace     bool
}

func Load() (Config, error) {
	cfg := Config{
		Listen:        env("N2U_LISTEN", "M2U_LISTEN", "127.0.0.1:8095"),
		PublicBaseURL: env("N2U_PUBLIC_BASE_URL", "M2U_PUBLIC_BASE_URL", "https://n2usenet.virebent.art"),
		SMTP: SMTPConfig{
			Host:          env("N2U_SMTP_HOST", "M2U_SMTP_HOST", "mail2news.tcpreset.net"),
			Port:          envInt("N2U_SMTP_PORT", "M2U_SMTP_PORT", 25),
			Recipient:     env("N2U_SMTP_RECIPIENT", "M2U_SMTP_RECIPIENT", "mail2news@mail2news.tcpreset.net"),
			EnvelopeFrom:  env("N2U_SMTP_ENVELOPE_FROM", "M2U_SMTP_ENVELOPE_FROM", "n2usenet@virebent.art"),
			HELO:          env("N2U_SMTP_HELO", "M2U_SMTP_HELO", "n2usenet.virebent.art"),
			TLSServerName: env("N2U_SMTP_TLS_SERVER_NAME", "M2U_SMTP_TLS_SERVER_NAME", "mail.tcpreset.net"),
			RequireTLS:    envBool("N2U_SMTP_REQUIRE_TLS", "M2U_SMTP_REQUIRE_TLS", true),
			ImplicitTLS:   envBool("N2U_SMTP_IMPLICIT_TLS", "M2U_SMTP_IMPLICIT_TLS", false),
			Timeout:       envDuration("N2U_SMTP_TIMEOUT", "M2U_SMTP_TIMEOUT", 90*time.Second),
			DryRun:        envBool("N2U_DRY_RUN", "M2U_DRY_RUN", false),
		},
		Nym: NymConfig{
			Enabled:          envBool("N2U_NYM_ENABLED", "M2U_NYM_ENABLED", true),
			Managed:          envBool("N2U_NYM_MANAGED", "M2U_NYM_MANAGED", false),
			Binary:           env("N2U_NYM_BINARY", "M2U_NYM_BINARY", "nym-socks5-client"),
			Provider:         env("N2U_NYM_PROVIDER", "M2U_NYM_PROVIDER", ""),
			HomeDir:          env("N2U_NYM_HOME", "M2U_NYM_HOME", "./data/nym"),
			ClientID:         env("N2U_NYM_CLIENT_ID", "M2U_NYM_CLIENT_ID", "n2usenet"),
			SocksAddr:        env("N2U_NYM_SOCKS", "M2U_NYM_SOCKS", "127.0.0.1:11080"),
			AnonymousReplies: envBool("N2U_NYM_ANONYMOUS_REPLIES", "M2U_NYM_ANONYMOUS_REPLIES", true),
			StartupTimeout:   envDuration("N2U_NYM_STARTUP_TIMEOUT", "M2U_NYM_STARTUP_TIMEOUT", 120*time.Second),
		},
		Security: SecurityConfig{
			MinHashcashBits: envInt("N2U_HASHCASH_MIN_BITS", "M2U_HASHCASH_MIN_BITS", 20),
			MinMessageBytes: envInt("N2U_MIN_MESSAGE_BYTES", "M2U_MIN_MESSAGE_BYTES", 10),
			MaxMessageBytes: envInt("N2U_MAX_MESSAGE_BYTES", "M2U_MAX_MESSAGE_BYTES", 65536),
			MaxNewsgroups:   envInt("N2U_MAX_NEWSGROUPS", "M2U_MAX_NEWSGROUPS", 3),
			RateLimitCount:  envInt("N2U_RATE_LIMIT_COUNT", "M2U_RATE_LIMIT_COUNT", 10),
			RateLimitWindow: envDuration("N2U_RATE_LIMIT_WINDOW", "M2U_RATE_LIMIT_WINDOW", time.Hour),
			TrustProxy:      envBool("N2U_TRUST_PROXY", "M2U_TRUST_PROXY", true),
			SecureCookies:   envBool("N2U_SECURE_COOKIES", "M2U_SECURE_COOKIES", true),
			MessageIDDomain: env("N2U_MESSAGE_ID_DOMAIN", "M2U_MESSAGE_ID_DOMAIN", "n2usenet.virebent.art"),
			IdenticonsCLI:   env("N2U_IDENTICONS_CLI", "M2U_IDENTICONS_CLI", "/usr/local/bin/identicons-cli"),
			RequireFace:     envBool("N2U_REQUIRE_FACE", "M2U_REQUIRE_FACE", true),
		},
	}
	if err := cfg.Validate(); err != nil {
		return Config{}, err
	}
	return cfg, nil
}

func (c Config) Validate() error {
	if _, _, err := net.SplitHostPort(c.Listen); err != nil {
		return fmt.Errorf("invalid N2U_LISTEN: %w", err)
	}
	if c.SMTP.Host == "" {
		return fmt.Errorf("N2U_SMTP_HOST is required")
	}
	if c.SMTP.Port < 1 || c.SMTP.Port > 65535 {
		return fmt.Errorf("invalid N2U_SMTP_PORT")
	}
	if c.SMTP.Recipient == "" {
		return fmt.Errorf("N2U_SMTP_RECIPIENT is required")
	}
	if c.SMTP.EnvelopeFrom == "" {
		return fmt.Errorf("N2U_SMTP_ENVELOPE_FROM is required")
	}
	if c.SMTP.RequireTLS && c.SMTP.TLSServerName == "" {
		return fmt.Errorf("N2U_SMTP_TLS_SERVER_NAME is required when TLS is required")
	}
	if c.Nym.Enabled {
		if _, _, err := net.SplitHostPort(c.Nym.SocksAddr); err != nil {
			return fmt.Errorf("invalid N2U_NYM_SOCKS: %w", err)
		}
		if c.Nym.Managed && c.Nym.Provider == "" {
			return fmt.Errorf("N2U_NYM_PROVIDER is required when N2U_NYM_MANAGED=true")
		}
	}
	if c.Security.MinHashcashBits < 1 || c.Security.MinHashcashBits > 32 {
		return fmt.Errorf("invalid N2U_HASHCASH_MIN_BITS")
	}
	if c.Security.MinMessageBytes < 0 || c.Security.MaxMessageBytes <= c.Security.MinMessageBytes {
		return fmt.Errorf("invalid message size limits")
	}
	if c.Security.MaxNewsgroups < 1 || c.Security.MaxNewsgroups > 10 {
		return fmt.Errorf("invalid N2U_MAX_NEWSGROUPS")
	}
	if c.Security.RateLimitCount < 1 {
		return fmt.Errorf("invalid N2U_RATE_LIMIT_COUNT")
	}
	if c.Security.RequireFace && c.Security.IdenticonsCLI == "" {
		return fmt.Errorf("N2U_IDENTICONS_CLI is required when N2U_REQUIRE_FACE=true")
	}
	return nil
}

func env(primary, legacy, fallback string) string {
	v, ok := envValue(primary, legacy)
	if !ok {
		return fallback
	}
	return v
}

func envBool(primary, legacy string, fallback bool) bool {
	v, ok := envValue(primary, legacy)
	if !ok {
		return fallback
	}
	switch strings.ToLower(v) {
	case "1", "true", "yes", "on":
		return true
	case "0", "false", "no", "off":
		return false
	default:
		return fallback
	}
}

func envInt(primary, legacy string, fallback int) int {
	v, ok := envValue(primary, legacy)
	if !ok {
		return fallback
	}
	n, err := strconv.Atoi(v)
	if err != nil {
		return fallback
	}
	return n
}

func envDuration(primary, legacy string, fallback time.Duration) time.Duration {
	v, ok := envValue(primary, legacy)
	if !ok {
		return fallback
	}
	if d, err := time.ParseDuration(v); err == nil {
		return d
	}
	if n, err := strconv.Atoi(v); err == nil {
		return time.Duration(n) * time.Second
	}
	return fallback
}

func envValue(primary, legacy string) (string, bool) {
	for _, key := range []string{primary, legacy} {
		v := strings.TrimSpace(os.Getenv(key))
		if v != "" {
			return v, true
		}
	}
	return "", false
}
