package backend

import (
	"flag"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"strings"

	"github.com/BurntSushi/toml"
)

type Config struct {
	AESKeyFile        string
	ArticleDir        string
	AtomFile          string
	ConfigFile        string
	DBName            string
	Description       string
	Domain            string
	FirebaseKey       string
	ImgDir            string
	Link              string
	LogFile           string
	PDFDir            string
	Port              string
	Title             string
	Version           string
	WebDir            string
	CookieExpiryHours int
	MaxBannerHeight   int
	MaxBannerWidth    int
	MaxImgHeight      int
	MaxImgWidth       int
}

func newConfig() *Config {
	return &Config{
		AESKeyFile:        "/var/www/cpolis/aes.key",
		ArticleDir:        "/var/www/cpolis/articles",
		AtomFile:          "/var/www/cpolis/cpolis.atom",
		ConfigFile:        "/etc/cpolis/config.toml",
		CookieExpiryHours: 24 * 30,
		DBName:            "cpolis",
		FirebaseKey:       "/etc/cpolis/serviceAccountKey.json",
		ImgDir:            "/var/www/cpolis/images",
		LogFile:           "/var/log/cpolis.log",
		MaxBannerHeight:   1080,
		MaxBannerWidth:    1920,
		MaxImgHeight:      1080,
		MaxImgWidth:       1920,
		PDFDir:            "/var/www/cpolis/pdfs",
		Port:              ":1664",
		Version:           "v0.16.0",
		WebDir:            "/var/www/cpolis/web",
	}
}

func mkDir(path string, perm fs.FileMode) (string, error) {
	name := filepath.Base(path)

	path, err := filepath.Abs(path)
	if err != nil {
		return "", fmt.Errorf("error finding absolute path for %v directory: %v", name, err)
	}
	if err = os.MkdirAll(path, perm); err != nil {
		return "", fmt.Errorf("error creating %v directory: %v", name, err)
	}

	return path, nil
}

func mkFile(path string, filePerm, dirPerm fs.FileMode) (string, error) {
	var err error

	path, err = filepath.Abs(path)
	if err != nil {
		return "", fmt.Errorf("error finding absolute path for %v: %v", path, err)
	}

	_, err = os.Stat(path)
	if os.IsNotExist(err) {
		dir := filepath.Dir(path)
		if err = os.MkdirAll(dir, dirPerm); err != nil {
			return "", fmt.Errorf("error creating %v: %v", dir, err)
		}

		fileName := filepath.Base(path)
		file, err := os.Create(filepath.Join(dir, fileName))
		if err != nil {
			return "", fmt.Errorf("error creating %v: %v", fileName, err)
		}
		defer file.Close()

		if err = file.Chmod(filePerm); err != nil {
			return "", fmt.Errorf("error setting permissions for %v: %v", fileName, err)
		}
	}

	return path, nil
}

func (c *Config) handleCliArgs() error {
	var port int
	var err error

	flag.StringVar(&c.AESKeyFile, "aes", c.AESKeyFile, "aes key file")
	flag.StringVar(&c.ArticleDir, "articles", c.ArticleDir, "articles directory")
	flag.StringVar(&c.AtomFile, "feed", c.AtomFile, "atom feed file")
	flag.StringVar(&c.ConfigFile, "config", c.ConfigFile, "config file")
	flag.StringVar(&c.DBName, "db", c.DBName, "DB name")
	flag.StringVar(&c.Description, "desc", c.Description, "channel description")
	flag.StringVar(&c.Domain, "domain", c.Domain, "domain name")
	flag.StringVar(&c.FirebaseKey, "firebase", c.FirebaseKey, "Firebase service account key file")
	flag.StringVar(&c.ImgDir, "images", c.ImgDir, "images directory")
	flag.StringVar(&c.Link, "link", c.Link, "channel Link")
	flag.StringVar(&c.LogFile, "log", c.LogFile, "log file")
	flag.StringVar(&c.PDFDir, "pdfs", c.PDFDir, "pdf directory")
	flag.StringVar(&c.Title, "title", c.Title, "channel title")
	flag.StringVar(&c.WebDir, "web", c.WebDir, "web directory")
	flag.IntVar(&c.CookieExpiryHours, "cookie-expiry-hours", c.CookieExpiryHours, "cookies expire after this amount of hours")
	flag.IntVar(&c.MaxBannerHeight, "banner-height", c.MaxBannerHeight, "maximum banner height")
	flag.IntVar(&c.MaxBannerWidth, "banner-width", c.MaxBannerWidth, "maximum banner width")
	flag.IntVar(&c.MaxImgHeight, "img-height", c.MaxImgHeight, "maximum image height")
	flag.IntVar(&c.MaxImgWidth, "img-width", c.MaxImgWidth, "maximum image width")
	flag.IntVar(&port, "port", port, "port")
	flag.Parse()

	if port != 0 {
		c.Port = fmt.Sprint(":", port)
	}

	c.ConfigFile, err = mkFile(c.ConfigFile, 0600, 0700)
	if err != nil {
		return fmt.Errorf("error setting up file: %v", err)
	}

	return nil
}

func (c *Config) handleFile(configFile string) error {
	_, err := toml.DecodeFile(configFile, c)
	if err != nil {
		return fmt.Errorf("error reading config file: %v", err)
	}

	return nil
}

func (c *Config) setupConfig(cliConfig *Config) error {
	var err error
	defaultConfig := newConfig()

	if cliConfig.AESKeyFile != defaultConfig.AESKeyFile {
		c.AESKeyFile = cliConfig.AESKeyFile
	}
	c.AESKeyFile, err = filepath.Abs(c.AESKeyFile)
	if err != nil {
		return fmt.Errorf("error setting absolute filepath for AESKeyFile: %v", err)
	}
	c.AESKeyFile, err = mkFile(c.AESKeyFile, 0600, 0700)
	if err != nil {
		return fmt.Errorf("error setting up file: %v", err)
	}

	if cliConfig.ArticleDir != defaultConfig.ArticleDir {
		c.ArticleDir = cliConfig.ArticleDir
	}
	c.ArticleDir, err = filepath.Abs(c.ArticleDir)
	if err != nil {
		return fmt.Errorf("error setting absolute filepath for ArticleDir: %v", err)
	}
	c.ArticleDir, err = mkDir(c.ArticleDir, 0700)
	if err != nil {
		return fmt.Errorf("error setting up directory: %v", err)
	}

	if cliConfig.AtomFile != defaultConfig.AtomFile {
		c.AtomFile = cliConfig.AtomFile
	}
	c.AtomFile, err = filepath.Abs(c.AtomFile)
	if err != nil {
		return fmt.Errorf("error setting absolute filepath for AtomFile: %v", err)
	}
	c.AtomFile, err = mkFile(c.AtomFile, 0644, 0744)
	if err != nil {
		return fmt.Errorf("error setting up file: %v", err)
	}

	if cliConfig.CookieExpiryHours != defaultConfig.CookieExpiryHours {
		c.CookieExpiryHours = cliConfig.CookieExpiryHours
	}

	if cliConfig.DBName != defaultConfig.DBName {
		c.DBName = cliConfig.DBName
	}

	if cliConfig.Description != defaultConfig.Description {
		c.Description = cliConfig.Description
	}

	if cliConfig.Domain != defaultConfig.Domain {
		c.Domain = cliConfig.Domain
	}
	domainStrings := strings.Split(c.Domain, ":")
	if domainStrings[0] != "http" && domainStrings[0] != "https" {
		c.Domain = "https://" + c.Domain
	}

	if cliConfig.FirebaseKey != defaultConfig.FirebaseKey {
		c.FirebaseKey = cliConfig.FirebaseKey
	}
	c.FirebaseKey, err = filepath.Abs(c.FirebaseKey)
	if err != nil {
		return fmt.Errorf("error setting absolute filepath for FirebaseKey: %v", err)
	}
	c.FirebaseKey, err = mkFile(c.FirebaseKey, 0600, 0700)
	if err != nil {
		return fmt.Errorf("error setting up file: %v", err)
	}

	if cliConfig.ImgDir != defaultConfig.ImgDir {
		c.ImgDir = cliConfig.ImgDir
	}
	c.ImgDir, err = filepath.Abs(c.ImgDir)
	if err != nil {
		return fmt.Errorf("error setting absolute filepath for PicsDir: %v", err)
	}
	c.ImgDir, err = mkDir(c.ImgDir, 0700)
	if err != nil {
		return fmt.Errorf("error setting up directory: %v", err)
	}

	if cliConfig.Link != defaultConfig.Link {
		c.Link = cliConfig.Link
	}

	if cliConfig.LogFile != defaultConfig.LogFile {
		c.LogFile = cliConfig.LogFile
	}
	c.LogFile, err = filepath.Abs(c.LogFile)
	if err != nil {
		return fmt.Errorf("error setting absolute filepath for LogFile: %v", err)
	}
	c.LogFile, err = mkFile(c.LogFile, 0600, 0700)
	if err != nil {
		return fmt.Errorf("error setting up file: %v", err)
	}

	if cliConfig.MaxBannerHeight != defaultConfig.MaxBannerHeight {
		c.MaxBannerHeight = cliConfig.MaxBannerHeight
	}

	if cliConfig.MaxBannerWidth != defaultConfig.MaxBannerWidth {
		c.MaxBannerWidth = cliConfig.MaxBannerWidth
	}

	if cliConfig.MaxImgHeight != defaultConfig.MaxImgHeight {
		c.MaxImgHeight = cliConfig.MaxImgHeight
	}

	if cliConfig.MaxImgWidth != defaultConfig.MaxImgWidth {
		c.MaxImgWidth = cliConfig.MaxImgWidth
	}

	if cliConfig.PDFDir != defaultConfig.PDFDir {
		c.PDFDir = cliConfig.PDFDir
	}
	c.PDFDir, err = filepath.Abs(c.PDFDir)
	if err != nil {
		return fmt.Errorf("error setting absolute filepath for PDFDir: %v", err)
	}
	c.PDFDir, err = mkDir(c.PDFDir, 0700)
	if err != nil {
		return fmt.Errorf("error setting up directory: %v", err)
	}

	if cliConfig.Port != defaultConfig.Port {
		c.Port = cliConfig.Port
	}

	if cliConfig.Title != defaultConfig.Title {
		c.Title = cliConfig.Title
	}

	if cliConfig.WebDir != defaultConfig.WebDir {
		c.WebDir = cliConfig.WebDir
	}
	c.WebDir, err = filepath.Abs(c.WebDir)
	if err != nil {
		return fmt.Errorf("error setting absolute filepath for WebDir: %v", err)
	}
	c.WebDir, err = mkDir(c.WebDir, 0700)
	if err != nil {
		return fmt.Errorf("error setting up directory: %v", err)
	}

	return nil
}

func HandleConfig() (*Config, error) {
	config := newConfig()
	cliConfig := newConfig()

	if err := cliConfig.handleCliArgs(); err != nil {
		return nil, fmt.Errorf("error handling cli arguments: %v", err)
	}

	err := config.handleFile(cliConfig.ConfigFile)
	if err != nil {
		return nil, fmt.Errorf("error reading configuration file: %v", err)
	}

	if err = config.setupConfig(cliConfig); err != nil {
		return nil, fmt.Errorf("error setting up files: %v", err)
	}

	return config, nil
}