package config

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"

	"code.nonshy.com/nonshy/website/pkg/encryption/keygen"
	"code.nonshy.com/nonshy/website/pkg/log"
	"github.com/google/uuid"
)

// Version of the config format - when new fields are added, it will attempt
// to write the settings.toml to disk so new defaults populate.
var currentVersion = 2

// Current loaded settings.json
var Current = DefaultVariable()

// Variable configuration attributes (loaded from settings.json).
type Variable struct {
	Version          int
	BaseURL          string
	AdminEmail       string
	CronAPIKey       string
	Mail             Mail
	Redis            Redis
	Database         Database
	BareRTC          BareRTC
	Maintenance      Maintenance
	Encryption       Encryption
	UseXForwardedFor bool
}

// DefaultVariable returns the default settings.json data.
func DefaultVariable() Variable {
	return Variable{
		BaseURL: "http://localhost:8080",
		Mail: Mail{
			Enabled: false,
			Host:    "localhost",
			Port:    25,
			From:    "no-reply@localhost",
		},
		Redis: Redis{
			Host: "localhost",
			Port: 6379,
		},
		Database: Database{
			SQLite:   "database.sqlite",
			Postgres: "host=localhost user=nonshy password=nonshy dbname=nonshy port=5679 sslmode=disable TimeZone=America/Los_Angeles",
		},
		CronAPIKey: uuid.New().String(),
	}
}

// LoadSettings loads the settings.json file or, if not existing, creates it with the default settings.
func LoadSettings() {
	var writeSettings bool

	if _, err := os.Stat(SettingsPath); !os.IsNotExist(err) {
		log.Info("Loading settings from %s", SettingsPath)
		content, err := ioutil.ReadFile(SettingsPath)
		if err != nil {
			panic(fmt.Sprintf("LoadSettings: couldn't read settings.json: %s", err))
		}

		var v Variable
		err = json.Unmarshal(content, &v)
		if err != nil {
			panic(fmt.Sprintf("LoadSettings: couldn't parse settings.json: %s", err))
		}

		Current = v
	} else {
		WriteSettings()
		log.Warn("NOTICE: Created default settings.json file - review it and configure mail servers and database!")
	}

	// If there is no DB configured, exit now.
	if !Current.Database.IsSQLite && !Current.Database.IsPostgres {
		log.Error("No database configured in settings.json. Choose SQLite or Postgres and update the DB connector string!")
		os.Exit(1)
	}

	// Initialize the AES encryption key.
	if len(Current.Encryption.AESKey) == 0 {
		log.Warn("NOTICE: rolling a random 32-byte (256-bit) AES encryption key for the settings file")
		aesKey, err := keygen.NewAESKey()
		if err != nil {
			log.Error("Couldn't generate AES key: %s", err)
			os.Exit(1)
		}
		Current.Encryption.AESKey = aesKey
		writeSettings = true
	}

	// Have we added new config fields? Save the settings.json.
	if Current.Version != currentVersion || writeSettings {
		log.Warn("New options are available for your settings.json file. Your settings will be re-saved now.")
		Current.Version = currentVersion
		if err := WriteSettings(); err != nil {
			log.Error("Couldn't write your settings.json file: %s", err)
		}
	}
}

// WriteSettings will commit the settings.json to disk.
func WriteSettings() error {
	log.Error("Note: initial settings.json was written to disk.")

	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	enc.SetIndent("", "    ")
	err := enc.Encode(Current)
	if err != nil {
		panic(fmt.Sprintf("WriteSettings: couldn't marshal settings: %s", err))
	}

	return ioutil.WriteFile(SettingsPath, buf.Bytes(), 0600)
}

// Mail settings.
type Mail struct {
	Enabled  bool
	Host     string // localhost
	Port     int    // 25
	From     string // noreply@localhost
	Username string // SMTP credentials
	Password string
}

// Redis settings.
type Redis struct {
	Host string
	Port int
	DB   int
}

// Database settings.
type Database struct {
	IsSQLite   bool
	IsPostgres bool
	SQLite     string
	Postgres   string
}

// BareRTC chat room settings.
type BareRTC struct {
	JWTSecret string
	URL       string
}

// Maintenance mode settings.
type Maintenance struct {
	PauseSignup      bool
	PauseLogin       bool
	PauseChat        bool
	PauseInteraction bool
}

// Encryption settings.
type Encryption struct {
	AESKey []byte
}
