package config

import (
	"fmt"
	"os"
	"strings"

	"gopkg.in/yaml.v3"
)

// APIConfig holds configuration for the HTTP API server.
type APIConfig struct {
	Bind            string   `yaml:"bind"`
	Secret          string   `yaml:"secret"`
	// SecureCookies sets the Secure flag on session cookies.
	// Enable when TLS is terminated at this server or at a trusted reverse proxy.
	SecureCookies   bool     `yaml:"secure_cookies"`
	// TrustedProxies is a list of IP addresses or CIDR ranges whose
	// X-Forwarded-For header is trusted. Empty = trust no proxy (use r.RemoteAddr).
	TrustedProxies  []string `yaml:"trusted_proxies"`
}

// MetricsConfig holds settings for the Prometheus /metrics endpoint.
type MetricsConfig struct {
	Enabled bool   `yaml:"enabled"` // default: false
	Token   string `yaml:"token"`   // optional Bearer token to protect /metrics
}

// Config is the full application configuration loaded from YAML.
type Config struct {
	Server     ServerConfig     `yaml:"server"`
	Storage    StorageConfig    `yaml:"storage"`
	Database   DatabaseConfig   `yaml:"database"`
	SMTP       SMTPConfig       `yaml:"smtp"`
	SMTPOut    SMTPOutConfig    `yaml:"smtp_out"`
	API        APIConfig        `yaml:"api"`
	Index      IndexConfig      `yaml:"index"`
	Audit      AuditConfig      `yaml:"audit"`
	Logging    LoggingConfig    `yaml:"logging"`
	IMAPServer IMAPServerConfig `yaml:"imap_server"`
	Metrics    MetricsConfig    `yaml:"metrics"`
	// PROJ-56: load-spreading for background jobs.
	OCR           OCRConfig           `yaml:"ocr"`
	IMAPScheduler IMAPSchedulerConfig `yaml:"imap_scheduler"`
	// PROJ-52: Vollständigkeits-Reconciliation (Zähl-Report Mailserver vs. Archiv).
	Reconciliation ReconciliationConfig `yaml:"reconciliation"`
}

// ReconciliationConfig holds settings for the daily completeness reconciliation
// job (PROJ-52). The job counts newly archived mails per source and per day and
// flags significant drops against the trailing 7-day average.
type ReconciliationConfig struct {
	// AlertThresholdPct is the relative drop (in percent, below the trailing
	// 7-day average) that triggers a `reconciliation_anomaly` audit entry.
	// A pointer so an explicit 0 (alert on any drop) is distinguishable from an
	// unset value (use the default).
	//   nil -> DefaultReconciliationThresholdPct (50%)
	//   0   -> alert whenever today's count is below the average
	//   1..100 -> alert when today's count is more than this % below the average
	AlertThresholdPct *int `yaml:"alert_threshold_pct,omitempty"`
}

// DefaultReconciliationThresholdPct is the default drop threshold (50% below the
// trailing 7-day average) applied when reconciliation.alert_threshold_pct is
// omitted from the config.
const DefaultReconciliationThresholdPct = 50

// ResolvedThresholdPct returns the effective alert threshold in percent.
// A nil or out-of-range value falls back to the default.
func (c ReconciliationConfig) ResolvedThresholdPct() int {
	if c.AlertThresholdPct == nil {
		return DefaultReconciliationThresholdPct
	}
	if *c.AlertThresholdPct < 0 || *c.AlertThresholdPct > 100 {
		return DefaultReconciliationThresholdPct
	}
	return *c.AlertThresholdPct
}

// OCRConfig holds settings for the background OCR worker (PROJ-56).
type OCRConfig struct {
	// PausedHours optionally defines a local-time window [start, end) during
	// which the OCR worker pauses processing (e.g. [8, 18] = paused 08:00–18:00).
	// Wrap-around windows are supported, e.g. [22, 6] = paused 22:00–06:00.
	// nil / unset = never pause (legacy behaviour: process immediately).
	PausedHours *[2]int `yaml:"paused_hours,omitempty"`
	// BatchMode (PROJ-58): when true, the continuous OCR worker is NOT started
	// at daemon boot and the upload path does not submit jobs to the in-memory
	// queue. OCR then runs only via the cron batch command
	// (`archivmail ocr-reprocess --status pending`). New mails stay
	// ocr_status='pending' in the DB until the next cron run.
	// false (default) = legacy behaviour (immediate, continuous processing).
	BatchMode bool `yaml:"batch_mode"`
}

// IMAPSchedulerConfig holds settings for the automatic IMAP sync scheduler (PROJ-56).
type IMAPSchedulerConfig struct {
	// JitterSeconds spreads the per-account sync start over a deterministic
	// offset derived from the account ID, so N accounts on the same interval
	// don't all poll on the same minute boundary.
	// A pointer so the unset case (use default) is distinguishable from an
	// explicit 0 (disable jitter).
	//   nil  -> DefaultIMAPJitterSeconds (240s window)
	//   0    -> jitter disabled (legacy behaviour)
	//   >0   -> jitter window in seconds
	JitterSeconds *int `yaml:"jitter_seconds,omitempty"`
}

// DefaultIMAPJitterSeconds is the jitter window applied when imap_scheduler
// is omitted entirely from the config (4 minutes).
const DefaultIMAPJitterSeconds = 240

// ResolvedJitterSeconds returns the effective jitter window. nil falls back to
// the default; an explicit 0 (or negative) means jitter is disabled.
func (c IMAPSchedulerConfig) ResolvedJitterSeconds() int {
	if c.JitterSeconds == nil {
		return DefaultIMAPJitterSeconds
	}
	if *c.JitterSeconds < 0 {
		return 0
	}
	return *c.JitterSeconds
}

// IMAPServerConfig holds settings for the embedded read-only IMAP archive server.
type IMAPServerConfig struct {
	Enabled bool   `yaml:"enabled"`
	Bind    string `yaml:"bind"`     // plain: ":1143", TLS: ":993"
	TLSCert string `yaml:"tls_cert"` // path to PEM certificate; if set, TLS is enabled
	TLSKey  string `yaml:"tls_key"`  // path to PEM private key
	FQDN    string `yaml:"-"`        // set at runtime from server.fqdn
}

// ServerConfig holds port settings for the main services.
type ServerConfig struct {
	FQDN     string `yaml:"fqdn"`      // Fully Qualified Domain Name — used in SMTP EHLO, IMAP greeting, and generated links
	APIPort  int    `yaml:"api_port"`
	SMTPPort int    `yaml:"smtp_port"`
}

// SMTPOutConfig holds settings for outgoing email (password reset, invitations).
type SMTPOutConfig struct {
	Host     string `yaml:"host"`
	Port     int    `yaml:"port"`
	User     string `yaml:"user"`
	Password string `yaml:"password"`
	TLS      bool   `yaml:"tls"`
	From     string `yaml:"from"` // e.g. "archivmail <noreply@firma.de>"
}

// StorageConfig holds file system paths for email storage.
type StorageConfig struct {
	StorePath     string `yaml:"store_path"`
	AStorePath    string `yaml:"astore_path"`
	Keyfile       string `yaml:"keyfile"`
	RetentionDays    int  `yaml:"retention_days"`     // 0 = kein Lock (GoBD-Compliance: z.B. 3650 für 10 Jahre)
	MinRetentionDays int  `yaml:"min_retention_days"` // PROJ-51: globale Mindestfrist; Regeln/Tenants dürfen nur verlängern (0 = keine)
	Compress         bool `yaml:"compress"`           // gzip-Kompression vor AES-256-GCM (spart ~40-60% Disk)
}

// DatabaseConfig holds PostgreSQL connection settings.
type DatabaseConfig struct {
	Host     string `yaml:"host"`
	Port     int    `yaml:"port"`
	Name     string `yaml:"name"`
	User     string `yaml:"user"`
	Password string `yaml:"password"`
	SSLMode  string `yaml:"sslmode"`
}

// DSN builds a PostgreSQL connection string from the config fields.
func (d DatabaseConfig) DSN() string {
	return fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s",
		d.User, d.Password, d.Host, d.Port, d.Name, d.SSLMode)
}

// SMTPConfig holds settings for the embedded SMTP server.
// SEC-26: AllowedIPs uses fail-closed logic. An empty list means NO IP is
// allowed to connect. To accept from any IP, explicitly set:
//   allowed_ips: ["0.0.0.0/0", "::/0"]
type SMTPConfig struct {
	Enabled         bool     `yaml:"enabled"`
	Bind            string   `yaml:"bind"`
	Domain          string   `yaml:"domain"`
	TLSCert         string   `yaml:"tls_cert"`
	TLSKey          string   `yaml:"tls_key"`
	MaxSizeMB       int      `yaml:"max_size_mb"`
	AllowedIPs      []string `yaml:"allowed_ips"`
	TenantRouting   string   `yaml:"tenant_routing"`    // "domain" or "default"
	DefaultTenantID int64    `yaml:"default_tenant_id"` // used when routing is "default" or domain lookup fails
}

// IndexConfig holds full-text index settings.
type IndexConfig struct {
	Path           string `yaml:"path"`
	Backend        string `yaml:"backend"`
	BatchSize      int    `yaml:"batch_size"`
	AsyncQueueSize int    `yaml:"async_queue_size"`
	ManticoreDSN   string `yaml:"manticore_dsn"` // DSN for Manticore backend (default: "manticore@tcp(127.0.0.1:9306)/?charset=utf8mb4")
	// BatchMode (PROJ-58): when true, the continuous index worker is NOT started
	// at daemon boot and the upload path does not submit documents to the
	// in-memory queue. Indexing then runs only via the cron batch command
	// (`archivmail index-pending`). New mails stay indexed_at IS NULL in the DB
	// until the next cron run.
	// false (default) = legacy behaviour (immediate, continuous indexing).
	BatchMode bool `yaml:"batch_mode"`
}

// DefaultAuditLogPath is the default location of the append-only JSON-Lines
// audit log file (PROJ-48) when audit.log_path is not configured.
const DefaultAuditLogPath = "/var/log/archivmail/audit.log"

// AuditConfig holds audit log settings.
type AuditConfig struct {
	LogPath       string `yaml:"log_path"`
	RetentionDays int    `yaml:"retention_days"`
}

// ResolvedLogPath returns the configured audit log file path, falling back to
// DefaultAuditLogPath when unset.
func (a AuditConfig) ResolvedLogPath() string {
	if strings.TrimSpace(a.LogPath) == "" {
		return DefaultAuditLogPath
	}
	return a.LogPath
}

// LoggingConfig holds application logging settings.
type LoggingConfig struct {
	Path  string `yaml:"path"`
	Level string `yaml:"level"`
}

// Load reads a YAML config file from path and returns a parsed Config.
func Load(path string) (*Config, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var cfg Config
	if err := yaml.Unmarshal(data, &cfg); err != nil {
		return nil, err
	}
	return &cfg, nil
}
