package config

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
)

type Config struct {
	Server   ServerConfig   `json:"server"`
	Database DatabaseConfig `json:"database"`
	Content  ContentConfig  `json:"content"`
	Web      WebConfig      `json:"web"`
	Auth     AuthConfig     `json:"auth"`
	Email    EmailConfig    `json:"email"`
	LogLevel string         `json:"logLevel"`
}

type ServerConfig struct {
	Addr string `json:"addr"`
}

type DatabaseConfig struct {
	Path       string `json:"path"`
	PrimaryURL string `json:"primaryUrl"`
	AuthToken  string `json:"authToken"`
}

type ContentConfig struct {
	SourceDir string `json:"sourceDir"`
	StoreDir  string `json:"storeDir"`
}

type WebConfig struct {
	DistDir    string `json:"distDir"`
	DevViteURL string `json:"devViteUrl"`
}

type AuthConfig struct {
	PublicOrigin string `json:"publicOrigin"`
}

type EmailConfig struct {
	SMTPHost string `json:"smtpHost"`
	SMTPPort int    `json:"smtpPort"`
	From     string `json:"from"`
}

func Default() Config {
	return Config{
		Server: ServerConfig{
			Addr: ":8080",
		},
		Database: DatabaseConfig{
			Path: filepath.Join("..", "..", "data", "db.sqlite"),
		},
		Content: ContentConfig{
			SourceDir: filepath.Join("..", "..", "content"),
			StoreDir:  filepath.Join("..", "..", "data", "files"),
		},
		Web: WebConfig{
			DistDir:    filepath.Join("..", "web", "dist"),
			DevViteURL: os.Getenv("CAIRNQUIRE_DEV_VITE_URL"),
		},
		Auth: AuthConfig{
			PublicOrigin: "http://localhost:8080",
		},
		Email: EmailConfig{
			SMTPHost: "localhost",
			SMTPPort: 1025,
			From:     "notifications@cairnquire.local",
		},
		LogLevel: "INFO",
	}
}

func Load() (Config, error) {
	cfg := Default()

	if configPath := os.Getenv("CAIRNQUIRE_CONFIG"); configPath != "" {
		if err := loadFile(configPath, &cfg); err != nil {
			return Config{}, err
		}
	}

	overrideString(&cfg.Server.Addr, "CAIRNQUIRE_SERVER_ADDR")
	overrideString(&cfg.Database.Path, "CAIRNQUIRE_DATABASE_PATH")
	overrideString(&cfg.Database.PrimaryURL, "CAIRNQUIRE_DATABASE_PRIMARY_URL")
	overrideString(&cfg.Database.AuthToken, "CAIRNQUIRE_DATABASE_AUTH_TOKEN")
	overrideString(&cfg.Content.SourceDir, "CAIRNQUIRE_CONTENT_SOURCE_DIR")
	overrideString(&cfg.Content.StoreDir, "CAIRNQUIRE_CONTENT_STORE_DIR")
	overrideString(&cfg.Web.DistDir, "CAIRNQUIRE_WEB_DIST_DIR")
	overrideString(&cfg.Web.DevViteURL, "CAIRNQUIRE_DEV_VITE_URL")
	overrideString(&cfg.Auth.PublicOrigin, "CAIRNQUIRE_PUBLIC_ORIGIN")
	overrideString(&cfg.Email.SMTPHost, "CAIRNQUIRE_EMAIL_SMTP_HOST")
	if port := os.Getenv("CAIRNQUIRE_EMAIL_SMTP_PORT"); port != "" {
		if p, err := strconv.Atoi(port); err == nil {
			cfg.Email.SMTPPort = p
		}
	}
	overrideString(&cfg.Email.From, "CAIRNQUIRE_EMAIL_FROM")
	overrideString(&cfg.LogLevel, "CAIRNQUIRE_LOG_LEVEL")

	if cfg.Server.Addr == "" {
		return Config{}, fmt.Errorf("server.addr must not be empty")
	}

	return cfg, nil
}

func loadFile(path string, cfg *Config) error {
	file, err := os.Open(path)
	if err != nil {
		return fmt.Errorf("open config file %q: %w", path, err)
	}
	defer file.Close()

	if err := json.NewDecoder(file).Decode(cfg); err != nil {
		return fmt.Errorf("decode config file %q: %w", path, err)
	}

	return nil
}

func overrideString(target *string, key string) {
	if value := os.Getenv(key); value != "" {
		*target = value
	}
}
