package config

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"gopkg.in/ini.v1"
)

type Config struct {
	// SERVER equivalent settings
	Host             string
	Port             int
	SecretKey        string
	Debug            bool
	MaxContentLength int64 // in bytes
	URLPrefix        string

	// MD Notes App settings
	AppName                string
	NotesDir               string
	NotesDirHideSidepane   []string
	NotesDirSkip           []string
	ImagesHide             bool
	ImageStorageMode       int
	ImageStoragePath       string
	ImageSubfolderName     string
	AllowedImageExtensions []string
	AllowedFileExtensions  []string

	// Visibility settings
	ShowImagesInTree   bool
	ShowFilesInTree    bool
	ShowImagesInFolder bool
	ShowFilesInFolder  bool

	// Database settings
	DBType string
	DBPath string

	// Auth settings
	RequireAdminActivation   bool
	RequireEmailConfirmation bool
	MFAEnabledByDefault      bool

	// Email (SMTP) settings
	SMTPHost     string
	SMTPPort     int
	SMTPUsername string
	SMTPPassword string
	SMTPSender   string
	SMTPUseTLS   bool

	// Security settings (failed-login thresholds and auto-ban config)
	PwdFailuresThreshold  int
	MFAFailuresThreshold  int
	FailuresWindowMinutes int
	AutoBanDurationHours  int
	AutoBanPermanent      bool
}

var defaultConfig = map[string]map[string]string{
	"SERVER": {
		"HOST":               "0.0.0.0",
		"PORT":               "3000",
		"SECRET_KEY":         "change-this-secret-key",
		"DEBUG":              "false",
		"MAX_CONTENT_LENGTH": "16", // in MB
		"URL_PREFIX":         "",
	},
	"MD_NOTES_APP": {
		"APP_NAME":                 "Gobsidian",
		"NOTES_DIR":                "notes",
		"NOTES_DIR_HIDE_SIDEPANE":  "attached, images",
		"NOTES_DIR_SKIP":           "secret, private",
		"IMAGES_HIDE":              "false",
		"IMAGE_STORAGE_MODE":       "1",
		"IMAGE_STORAGE_PATH":       "images",
		"IMAGE_SUBFOLDER_NAME":     "attached",
		"ALLOWED_IMAGE_EXTENSIONS": "jpg, jpeg, png, webp, gif",
		"ALLOWED_FILE_EXTENSIONS":  "txt, pdf, html, json, yaml, yml, conf, csv, cmd, bat, sh",
		// Visibility defaults
		"SHOW_IMAGES_IN_TREE":   "false",
		"SHOW_FILES_IN_TREE":    "true",
		"SHOW_IMAGES_IN_FOLDER": "true",
		"SHOW_FILES_IN_FOLDER":  "true",
	},
	"DATABASE": {
		"TYPE": "sqlite",
		"PATH": "data/gobsidian.db",
	},
	"AUTH": {
		"REQUIRE_ADMIN_ACTIVATION":   "true",
		"REQUIRE_EMAIL_CONFIRMATION": "true",
		"MFA_ENABLED_BY_DEFAULT":     "false",
	},
	"EMAIL": {
		"SMTP_HOST":     "",
		"SMTP_PORT":     "587",
		"SMTP_USERNAME": "",
		"SMTP_PASSWORD": "",
		"SMTP_SENDER":   "",
		"SMTP_USE_TLS":  "true",
	},
	"SECURITY": {
		"PWD_FAILURES_THRESHOLD":  "5",
		"MFA_FAILURES_THRESHOLD":  "10",
		"FAILURES_WINDOW_MINUTES": "30",
		"AUTO_BAN_DURATION_HOURS": "12",
		"AUTO_BAN_PERMANENT":      "false",
	},
}

// exeDir returns the directory containing the running executable.
func exeDir() string {
	exe, err := os.Executable()
	if err != nil {
		wd, _ := os.Getwd()
		return wd
	}
	return filepath.Dir(exe)
}

func Load() (*Config, error) {
	baseDir := exeDir()
	// settings.ini lives next to the executable
	configPath := filepath.Join(baseDir, "settings.ini")

	// Ensure config file exists
	if err := ensureConfigFile(configPath); err != nil {
		return nil, fmt.Errorf("failed to ensure config file: %w", err)
	}

	// Load configuration
	cfg, err := ini.Load(configPath)
	if err != nil {
		return nil, fmt.Errorf("failed to load config file: %w", err)
	}

	config := &Config{}

	// Load SERVER section
	SERVERSection := cfg.Section("SERVER")
	config.Host = SERVERSection.Key("HOST").String()
	config.Port, _ = SERVERSection.Key("PORT").Int()
	config.SecretKey = SERVERSection.Key("SECRET_KEY").String()
	config.Debug, _ = SERVERSection.Key("DEBUG").Bool()
	maxContentMB, _ := SERVERSection.Key("MAX_CONTENT_LENGTH").Int()
	config.MaxContentLength = int64(maxContentMB) * 1024 * 1024 // Convert MB to bytes
	// Normalize URL prefix: "" or starts with '/' and no trailing '/'
	rawPrefix := strings.TrimSpace(SERVERSection.Key("URL_PREFIX").String())
	if rawPrefix == "/" { rawPrefix = "" }
	if rawPrefix != "" {
		if !strings.HasPrefix(rawPrefix, "/") { rawPrefix = "/" + rawPrefix }
		rawPrefix = strings.TrimRight(rawPrefix, "/")
	}
	config.URLPrefix = rawPrefix

	// Load MD_NOTES_APP section
	notesSection := cfg.Section("MD_NOTES_APP")
	config.AppName = notesSection.Key("APP_NAME").String()
	config.NotesDir = notesSection.Key("NOTES_DIR").String()

	// Parse comma-separated lists
	config.NotesDirHideSidepane = parseCommaSeparated(notesSection.Key("NOTES_DIR_HIDE_SIDEPANE").String())
	config.NotesDirSkip = parseCommaSeparated(notesSection.Key("NOTES_DIR_SKIP").String())
	config.AllowedImageExtensions = parseCommaSeparated(notesSection.Key("ALLOWED_IMAGE_EXTENSIONS").String())
	config.AllowedFileExtensions = parseCommaSeparated(notesSection.Key("ALLOWED_FILE_EXTENSIONS").String())

	config.ImagesHide, _ = notesSection.Key("IMAGES_HIDE").Bool()
	// New visibility flags (fallback to legacy IMAGES_HIDE for folder images if keys missing)
	config.ShowImagesInTree, _ = notesSection.Key("SHOW_IMAGES_IN_TREE").Bool()
	config.ShowFilesInTree, _ = notesSection.Key("SHOW_FILES_IN_TREE").Bool()
	if key := notesSection.Key("SHOW_IMAGES_IN_FOLDER"); key.String() != "" {
		config.ShowImagesInFolder, _ = key.Bool()
	} else {
		// fallback: if IMAGES_HIDE true => not shown
		config.ShowImagesInFolder = !config.ImagesHide
	}
	if key := notesSection.Key("SHOW_FILES_IN_FOLDER"); key.String() != "" {
		config.ShowFilesInFolder, _ = key.Bool()
	} else {
		config.ShowFilesInFolder = true
	}
	config.ImageStorageMode, _ = notesSection.Key("IMAGE_STORAGE_MODE").Int()
	config.ImageStoragePath = notesSection.Key("IMAGE_STORAGE_PATH").String()
	config.ImageSubfolderName = notesSection.Key("IMAGE_SUBFOLDER_NAME").String()

	// Convert relative paths to be next to the executable
	if !filepath.IsAbs(config.NotesDir) {
		config.NotesDir = filepath.Join(baseDir, config.NotesDir)
	}

	if !filepath.IsAbs(config.ImageStoragePath) && config.ImageStorageMode == 2 {
		config.ImageStoragePath = filepath.Join(baseDir, config.ImageStoragePath)
	}

	// Ensure these directories exist
	if err := os.MkdirAll(config.NotesDir, 0o755); err != nil {
		return nil, fmt.Errorf("failed to create notes directory: %w", err)
	}
	if config.ImageStorageMode == 2 {
		if err := os.MkdirAll(config.ImageStoragePath, 0o755); err != nil {
			return nil, fmt.Errorf("failed to create image storage directory: %w", err)
		}
	}

	// Load DATABASE section
	dbSection := cfg.Section("DATABASE")
	config.DBType = strings.ToLower(strings.TrimSpace(dbSection.Key("TYPE").String()))
	config.DBPath = dbSection.Key("PATH").String()
	if config.DBType == "sqlite" {
		if !filepath.IsAbs(config.DBPath) {
			config.DBPath = filepath.Join(baseDir, config.DBPath)
		}
		// ensure parent dir exists
		if err := os.MkdirAll(filepath.Dir(config.DBPath), 0o755); err != nil {
			return nil, fmt.Errorf("failed to create db directory: %w", err)
		}
	}

	// Load AUTH section
	authSection := cfg.Section("AUTH")
	config.RequireAdminActivation, _ = authSection.Key("REQUIRE_ADMIN_ACTIVATION").Bool()
	config.RequireEmailConfirmation, _ = authSection.Key("REQUIRE_EMAIL_CONFIRMATION").Bool()
	config.MFAEnabledByDefault, _ = authSection.Key("MFA_ENABLED_BY_DEFAULT").Bool()

	// Load EMAIL (SMTP) section
	emailSection := cfg.Section("EMAIL")
	config.SMTPHost = emailSection.Key("SMTP_HOST").String()
	config.SMTPPort, _ = emailSection.Key("SMTP_PORT").Int()
	config.SMTPUsername = emailSection.Key("SMTP_USERNAME").String()
	config.SMTPPassword = emailSection.Key("SMTP_PASSWORD").String()
	config.SMTPSender = emailSection.Key("SMTP_SENDER").String()
	config.SMTPUseTLS, _ = emailSection.Key("SMTP_USE_TLS").Bool()

	// Load SECURITY section
	secSection := cfg.Section("SECURITY")
	config.PwdFailuresThreshold, _ = secSection.Key("PWD_FAILURES_THRESHOLD").Int()
	config.MFAFailuresThreshold, _ = secSection.Key("MFA_FAILURES_THRESHOLD").Int()
	config.FailuresWindowMinutes, _ = secSection.Key("FAILURES_WINDOW_MINUTES").Int()
	config.AutoBanDurationHours, _ = secSection.Key("AUTO_BAN_DURATION_HOURS").Int()
	config.AutoBanPermanent, _ = secSection.Key("AUTO_BAN_PERMANENT").Bool()

	return config, nil
}

func ensureConfigFile(configPath string) error {
	// Check if file exists
	if _, err := os.Stat(configPath); os.IsNotExist(err) {
		// ensure parent dir exists
		if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
			return err
		}
		return createDefaultConfigFile(configPath)
	}

	// File exists, check if it has all required settings
	return updateConfigFile(configPath)
}

func createDefaultConfigFile(configPath string) error {
	cfg := ini.Empty()

	for sectionName, settings := range defaultConfig {
		section, err := cfg.NewSection(sectionName)
		if err != nil {
			return err
		}

		for key, value := range settings {
			section.NewKey(key, value)
		}
	}

	return cfg.SaveTo(configPath)
}

func updateConfigFile(configPath string) error {
	cfg, err := ini.Load(configPath)
	if err != nil {
		return err
	}

	updated := false

	for sectionName, settings := range defaultConfig {
		section := cfg.Section(sectionName)

		for key, defaultValue := range settings {
			if !section.HasKey(key) {
				section.NewKey(key, defaultValue)
				updated = true
			}
		}
	}

	if updated {
		return cfg.SaveTo(configPath)
	}

	return nil
}

func parseCommaSeparated(value string) []string {
	if value == "" {
		return []string{}
	}

	parts := strings.Split(value, ",")
	result := make([]string, 0, len(parts))

	for _, part := range parts {
		if trimmed := strings.TrimSpace(part); trimmed != "" {
			result = append(result, trimmed)
		}
	}

	return result
}

func (c *Config) SaveSetting(section, key, value string) error {
	configPath := filepath.Join(exeDir(), "settings.ini")
	cfg, err := ini.Load(configPath)
	if err != nil {
		return err
	}

	sec := cfg.Section(section)
	sec.Key(key).SetValue(value)

	// Update in-memory config based on section and key
	switch section {
	case "SERVER":
		switch key {
		case "HOST":
			c.Host = value
		case "PORT":
			if port, err := strconv.Atoi(value); err == nil {
				c.Port = port
			}
		case "SECRET_KEY":
			c.SecretKey = value
		case "DEBUG":
			c.Debug = value == "true"
		case "MAX_CONTENT_LENGTH":
			if size, err := strconv.ParseInt(value, 10, 64); err == nil {
				c.MaxContentLength = size * 1024 * 1024
			}
		case "URL_PREFIX":
			v := strings.TrimSpace(value)
			if v == "/" { v = "" }
			if v != "" {
				if !strings.HasPrefix(v, "/") { v = "/" + v }
				v = strings.TrimRight(v, "/")
			}
			c.URLPrefix = v
		}
	case "MD_NOTES_APP":
		switch key {
		case "APP_NAME":
			c.AppName = value
		case "NOTES_DIR":
			c.NotesDir = value
		case "NOTES_DIR_HIDE_SIDEPANE":
			c.NotesDirHideSidepane = parseCommaSeparated(value)
		case "NOTES_DIR_SKIP":
			c.NotesDirSkip = parseCommaSeparated(value)
		case "IMAGES_HIDE":
			c.ImagesHide = value == "true"
		case "IMAGE_STORAGE_MODE":
			if mode, err := strconv.Atoi(value); err == nil {
				c.ImageStorageMode = mode
			}
		case "IMAGE_STORAGE_PATH":
			c.ImageStoragePath = value
		case "IMAGE_SUBFOLDER_NAME":
			c.ImageSubfolderName = value
		case "ALLOWED_IMAGE_EXTENSIONS":
			c.AllowedImageExtensions = parseCommaSeparated(value)
		case "ALLOWED_FILE_EXTENSIONS":
			c.AllowedFileExtensions = parseCommaSeparated(value)
		case "SHOW_IMAGES_IN_TREE":
			c.ShowImagesInTree = value == "true"
		case "SHOW_FILES_IN_TREE":
			c.ShowFilesInTree = value == "true"
		case "SHOW_IMAGES_IN_FOLDER":
			c.ShowImagesInFolder = value == "true"
		case "SHOW_FILES_IN_FOLDER":
			c.ShowFilesInFolder = value == "true"
		}
	case "DATABASE":
		switch key {
		case "TYPE":
			c.DBType = strings.ToLower(strings.TrimSpace(value))
		case "PATH":
			c.DBPath = value
		}
	case "AUTH":
		switch key {
		case "REQUIRE_ADMIN_ACTIVATION":
			c.RequireAdminActivation = value == "true"
		case "REQUIRE_EMAIL_CONFIRMATION":
			c.RequireEmailConfirmation = value == "true"
		case "MFA_ENABLED_BY_DEFAULT":
			c.MFAEnabledByDefault = value == "true"
		}
	case "EMAIL":
		switch key {
		case "SMTP_HOST":
			c.SMTPHost = value
		case "SMTP_PORT":
			if v, err := strconv.Atoi(value); err == nil {
				c.SMTPPort = v
			}
		case "SMTP_USERNAME":
			c.SMTPUsername = value
		case "SMTP_PASSWORD":
			c.SMTPPassword = value
		case "SMTP_SENDER":
			c.SMTPSender = value
		case "SMTP_USE_TLS":
			c.SMTPUseTLS = value == "true"
		}
	case "SECURITY":
		switch key {
		case "PWD_FAILURES_THRESHOLD":
			if v, err := strconv.Atoi(value); err == nil {
				c.PwdFailuresThreshold = v
			}
		case "MFA_FAILURES_THRESHOLD":
			if v, err := strconv.Atoi(value); err == nil {
				c.MFAFailuresThreshold = v
			}
		case "FAILURES_WINDOW_MINUTES":
			if v, err := strconv.Atoi(value); err == nil {
				c.FailuresWindowMinutes = v
			}
		case "AUTO_BAN_DURATION_HOURS":
			if v, err := strconv.Atoi(value); err == nil {
				c.AutoBanDurationHours = v
			}
		case "AUTO_BAN_PERMANENT":
			c.AutoBanPermanent = value == "true"
		}
	}

	return cfg.SaveTo(configPath)
}
