package settings

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"

	"git.kirsle.net/apps/gophertype/pkg/console"
)

// Current holds the current app settings. When the app settings have never
// been initialized, this struct holds the default values with a random secret
// key. The config is not saved to DB until you call Save() on it.
var Current = Load()

// UserRoot is the folder path to the user web files.
var UserRoot string

// Spec singleton holds the app configuration.
type Spec struct {
	// Sets to `true` when the site's initial setup has run and an admin created.
	Initialized bool

	// Site information
	Title       string
	Description string
	Email       string // primary email for notifications
	NSFW        bool
	BaseURL     string

	// Blog settings
	PostsPerPage int

	// Mail settings
	MailEnabled  bool
	MailSender   string
	MailHost     string
	MailPort     int
	MailUsername string
	MailPassword string

	// Redis settings
	RedisEnabled bool
	RedisHost    string
	RedisPort    int
	RedisDB      int

	// Security
	SecretKey string
}

// Filename is the path where the settings.json file is saved to.
var (
	configFilename = ".settings.json"
	configPath     string
)

// SetFilename sets the config file path, to be inside the user root.
func SetFilename(userRoot string) error {
	path := filepath.Join(userRoot, configFilename)
	configPath = path

	// Initialize it for the first time?
	if _, err := os.Stat(path); os.IsNotExist(err) {
		console.Warn("No settings.json found; initializing a new settings file at %s", path)
		fh, err := os.Create(path)
		if err != nil {
			return fmt.Errorf("couldn't initialize settings.json at %s: %s", path, err)
		}

		Current.ToJSON(fh)
		fh.Close()
		return nil
	}

	// Read the stored file instead.
	fh, err := os.Open(path)
	if err != nil {
		return fmt.Errorf("couldn't read settings.json from %s: %s", path, err)
	}
	defer fh.Close()

	data, err := ioutil.ReadAll(fh)
	if err != nil {
		return fmt.Errorf("couldn't read data from settings.json at %s: %s", path, err)
	}

	spec, err := FromJSON(data)
	if err != nil {
		return fmt.Errorf("settings.json parse error from %s: %s", path, err)
	}

	Current = spec
	UserRoot = userRoot
	return nil
}

// Load gets or creates the App Settings.
func Load() Spec {
	var s = Spec{
		Title:        "Untitled Site",
		Description:  "Just another web blog.",
		SecretKey:    MakeSecretKey(),
		PostsPerPage: 20,

		RedisHost: "localhost",
		RedisPort: 6379,
	}

	return s
}

// FromJSON loads a settings JSON from bytes.
func FromJSON(data []byte) (Spec, error) {
	var s Spec
	err := json.Unmarshal(data, &s)
	return s, err
}

// ToJSON converts the settings spec to JSON.
func (s Spec) ToJSON(w io.Writer) error {
	enc := json.NewEncoder(w)
	enc.SetIndent("", "\t")
	err := enc.Encode(s)
	return err
}

// Save the settings to DB.
func (s Spec) Save() error {
	Current = s

	fh, err := os.Create(configPath)
	if err != nil {
		return err
	}
	defer fh.Close()

	return s.ToJSON(fh)
}

// MakeSecretKey generates a secret key for signing HTTP cookies.
func MakeSecretKey() string {
	keyLength := 32

	b := make([]byte, keyLength)
	rand.Read(b)
	return base64.URLEncoding.EncodeToString(b)
}
