// # Copyright (C) 2025 Francois Marier
//
// Email-Reminder is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 3 of the
// License, or (at your option) any later version.
//
// Email-Reminder is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Email-Reminder; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
// 02110-1301, USA.
package config

import (
	"bufio"
	_ "embed"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"launchpad.net/email-reminder/internal/util"
)

//go:embed version
var VersionNumber string

const (
	LegacyUserConfigFile = ".email-reminders"
	UserConfigDir        = ".config/email-reminder"
	UserConfigFile       = "reminders.xml"
	SpoolDir             = "/var/spool/email-reminder"
	systemWideConfigFile = "email-reminder.conf" // relative to /etc
)

type Config struct {
	SmtpServer    string
	SmtpUsername  string
	SmtpPassword  string
	MailFrom      string
	SendReminders bool
	SmtpSsl       bool

	// Limits to prevent resource exhaustion
	MaxEventsPerUser      int
	MaxRecipientsPerEvent int
	MaxXMLSizeBytes       int

	// Runtime flags
	Simulate bool
	Verbose  bool
}

var smtpServerRegex = regexp.MustCompile(`^([A-Za-z0-9][A-Za-z0-9.\-]*[A-Za-z0-9])$`)

func readConfig(dir string, filename string) Config {
	// Use the local machine's hostname so that the server name matches the self-signed cert that Debian creates for STARTTLS purposes.
	hostname, err := os.Hostname()
	if err != nil {
		hostname = "localhost"
	}

	// Default values
	config := Config{
		SendReminders: true,
		SmtpServer:    hostname,
		SmtpSsl:       false,
		SmtpUsername:  "",
		SmtpPassword:  "",
		MailFrom:      "root@localhost",

		MaxEventsPerUser:      10000,
		MaxRecipientsPerEvent: 200,
		MaxXMLSizeBytes:       50 * 1024 * 1024, // 50MB
	}

	file, err := os.OpenInRoot(dir, filename)
	if err != nil {
		log.Printf("failed to open system-wide config file in /etc: %v", err)
		log.Println("Using default configuration values")
		return config
	}
	defer file.Close()

	if info, err := file.Stat(); err == nil {
		permissions := info.Mode().Perm()
		if permissions&0024 != 0 {
			log.Printf("Warning: '%s' has insecure permissions: %o. This file could contain sensitive credentials and should be 640 at most.", file.Name(), permissions)
		}
	}

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		// Strip out comments
		tokens := strings.SplitN(scanner.Text(), "#", 2)
		line := strings.TrimSpace(tokens[0])

		if line == "" {
			// Skip blank lines
			continue
		}

		tokens = strings.Split(line, "=")
		if len(tokens) != 2 {
			// Stop processing invalid file
			log.Printf("Ignoring invalid line in %s: %s", filename, line)
			continue
		}

		key := strings.TrimSpace(tokens[0])
		value := strings.TrimSpace(tokens[1])
		switch key {
		case "send_reminders":
			b, err := util.ParseBool(value)
			if err == nil {
				config.SendReminders = b
			}
		case "smtp_server":
			if smtpServerRegex.MatchString(value) {
				config.SmtpServer = value
			} else {
				log.Printf("Ignoring invalid SMTP server '%s'", value)
			}
		case "smtp_ssl":
			b, err := util.ParseBool(value)
			if err == nil {
				config.SmtpSsl = b
			}
		case "smtp_username":
			if len(value) <= 128 {
				config.SmtpUsername = value
			} else {
				log.Printf("Ignoring invalid SMTP username: length exceeds 128 characters")
			}
		case "smtp_password":
			if len(value) <= 128 {
				config.SmtpPassword = value
			} else {
				log.Printf("Ignoring invalid SMTP password: length exceeds 128 characters")
			}
		case "mail_from":
			if err := util.ValidateEmail(value); err != nil {
				log.Printf("Ignoring invalid mail_from '%s': %v", value, err)
			} else {
				config.MailFrom = value
			}
		case "max_events_per_user":
			if n, err := strconv.Atoi(value); err != nil {
				log.Printf("Ignoring invalid max_events_per_user '%s': %v", value, err)
			} else if n <= 0 {
				log.Printf("Ignoring invalid max_events_per_user '%s': must be positive and non-zero", value)
			} else {
				config.MaxEventsPerUser = n
			}
		case "max_recipients_per_event":
			if n, err := strconv.Atoi(value); err != nil {
				log.Printf("Ignoring invalid max_recipients_per_event '%s': %v", value, err)
			} else if n <= 0 || n > 10_000 {
				log.Printf("Ignoring invalid max_recipients_per_event '%s': must be between 1 and 10000 inclusively", value)
			} else {
				config.MaxRecipientsPerEvent = n
			}
		case "max_xml_size_mb":
			if n, err := strconv.Atoi(value); err != nil {
				log.Printf("Ignoring invalid max_xml_size_mb '%s': %v", value, err)
			} else if n <= 0 || n > 1_000_000 {
				log.Printf("Ignoring invalid max_xml_size_mb '%s': must be between 1 and 1M inclusively", value)
			} else {
				config.MaxXMLSizeBytes = n * 1024 * 1024
			}
		}
	}

	if err := scanner.Err(); err != nil {
		log.Printf("Invalid system-wide config file %s: %s\n", filename, err)
	}
	return config
}

func ReadSystemWideConfig(simulate bool, verbose bool) Config {
	config := readConfig("/etc", systemWideConfigFile)
	config.Simulate = simulate
	config.Verbose = verbose
	return config
}

// Returns the name of the user's config file relative to homeDir or empty string if there isn't one
func GetUserConfigFilename(homeDir string) string {
	preferredFilename := filepath.Join(UserConfigDir, UserConfigFile)
	if _, err := os.Stat(filepath.Join(homeDir, preferredFilename)); err == nil {
		return preferredFilename
	}

	if _, err := os.Stat(filepath.Join(homeDir, LegacyUserConfigFile)); err == nil {
		return LegacyUserConfigFile
	}

	return ""
}
