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"`
	Auth     AuthConfig     `json:"auth"`
	Email    EmailConfig    `json:"email"`
	LogLevel string         `json:"logLevel"`
	DevMode  bool           `json:"devMode"`
}

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 AuthConfig struct {
	PublicOrigin string `json:"publicOrigin"`
}

type EmailConfig struct {
	// Provider selects the sender implementation. Valid values: "noop",
	// "smtp", "postmark", or "cloudflare". When empty, the loader infers
	// from credentials, then SMTPHost, then NoOp.
	Provider            string `json:"provider"`
	SMTPHost            string `json:"smtpHost"`
	SMTPPort            int    `json:"smtpPort"`
	PostmarkToken       string `json:"postmarkToken"`
	PostmarkAPIURL      string `json:"postmarkApiUrl"`
	CloudflareAccountID string `json:"cloudflareAccountId"`
	CloudflareAPIToken  string `json:"cloudflareApiToken"`
	CloudflareAPIURL    string `json:"cloudflareApiUrl"`
	From                string `json:"from"`
	InstanceName        string `json:"instanceName"`
}

// ResolvedProvider returns the concrete provider choice after applying the
// inference rule. Operators can set Provider explicitly to force a choice.
func (e EmailConfig) ResolvedProvider() string {
	if e.Provider != "" {
		return e.Provider
	}
	if e.CloudflareAPIToken != "" || e.CloudflareAccountID != "" {
		return "cloudflare"
	}
	if e.PostmarkToken != "" {
		return "postmark"
	}
	if e.SMTPHost != "" {
		return "smtp"
	}
	return "noop"
}

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"),
		},
		Auth: AuthConfig{
			PublicOrigin: "http://localhost:8080",
		},
		Email: EmailConfig{
			SMTPHost:     "localhost",
			SMTPPort:     1025,
			From:         "notifications@cairnquire.local",
			InstanceName: "Cairnquire",
		},
		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")
	if port := os.Getenv("PORT"); port != "" {
		cfg.Server.Addr = ":" + port
	}
	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.Auth.PublicOrigin, "CAIRNQUIRE_PUBLIC_ORIGIN")
	overrideString(&cfg.Email.SMTPHost, "CAIRNQUIRE_EMAIL_SMTP_HOST")
	overrideString(&cfg.Email.PostmarkToken, "CAIRNQUIRE_EMAIL_POSTMARK_TOKEN")
	overrideString(&cfg.Email.PostmarkAPIURL, "CAIRNQUIRE_EMAIL_POSTMARK_API_URL")
	overrideString(&cfg.Email.CloudflareAccountID, "CAIRNQUIRE_EMAIL_CLOUDFLARE_ACCOUNT_ID")
	overrideString(&cfg.Email.CloudflareAPIToken, "CAIRNQUIRE_EMAIL_CLOUDFLARE_API_TOKEN")
	overrideString(&cfg.Email.CloudflareAPIURL, "CAIRNQUIRE_EMAIL_CLOUDFLARE_API_URL")
	overrideString(&cfg.Email.Provider, "CAIRNQUIRE_EMAIL_PROVIDER")
	overrideString(&cfg.Email.InstanceName, "CAIRNQUIRE_INSTANCE_NAME")
	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 devMode := os.Getenv("CAIRNQUIRE_DEV_MODE"); devMode != "" {
		parsed, err := strconv.ParseBool(devMode)
		if err != nil {
			return Config{}, fmt.Errorf("parse CAIRNQUIRE_DEV_MODE: %w", err)
		}
		cfg.DevMode = parsed
	}

	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
	}
}
