package main

import (
	"errors"
	"fmt"
	"io/fs"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/spf13/viper"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

type DatabaseConfig struct {
	Host          string `mapstructure:"DB_HOST"`
	Username      string `mapstructure:"DB_USERNAME"`
	Password      string `mapstructure:"DB_PASSWORD"`
	Port          int    `mapstructure:"DB_PORT"`
	Database      string `mapstructure:"DB_DATABASE"`
	RetryAttempts uint   `mapstructure:"DB_RETRY_ATTEMPTS"`
}

func main() {
	log.Println("Start seeding")
	cfg, err := loadConfig()
	if err != nil {
		panic("failed to load config: " + err.Error())
	}
	if err = executeSeeds(cfg); err != nil {
		panic(err)
	}
	log.Println("Completed seeding")
}

func loadConfig() (config *DatabaseConfig, err error) {
	viper.SetDefault("DB_HOST", os.Getenv("DB_HOST"))
	viper.SetDefault("DB_USERNAME", os.Getenv("DB_USERNAME"))
	viper.SetDefault("DB_PASSWORD", os.Getenv("DB_PASSWORD"))
	viper.SetDefault("DB_PORT", os.Getenv("DB_PORT"))
	viper.SetDefault("DB_DATABASE", os.Getenv("DB_DATABASE"))
	viper.SetDefault("DB_RETRY_ATTEMPTS", os.Getenv("DB_RETRY_ATTEMPTS"))

	hasConfigFile := false
	if _, err := os.Stat(".env"); err == nil {
		viper.SetConfigFile(".env")
		viper.AddConfigPath(".")
		_ = viper.MergeInConfig()
		hasConfigFile = true
	}

	if _, err := os.Stat("./secrets"); err == nil {
		_ = filepath.Walk("./secrets", func(path string, info fs.FileInfo, err error) error {
			if strings.HasPrefix(info.Name(), ".env") {
				viper.SetConfigFile("./secrets/" + info.Name())
				viper.SetConfigType("env")
				_ = viper.MergeInConfig()
				hasConfigFile = true
			}
			return nil
		})
	}

	if hasConfigFile {
		if err = viper.ReadInConfig(); err != nil {
			return
		}
	}

	if err = viper.Unmarshal(&config); err != nil {
		return
	}

	return
}

func executeSeeds(cfg *DatabaseConfig) error {
	var paths []string

	err := filepath.Walk("db/migrations/seeds", func(path string, info fs.FileInfo, err error) error {
		if strings.HasSuffix(info.Name(), ".sql") {
			paths = append(paths, path)
		}
		return nil
	})
	if err != nil {
		return errors.New(fmt.Sprintf("Failed to access seeding files with error: %s", err.Error()))
	}

	for _, path := range paths {
		if err := executeSeed(path, cfg); err != nil {
			return err
		}
	}
	return nil
}

func executeSeed(path string, cfg *DatabaseConfig) error {
	f, err := os.Open(path)
	if err != nil {
		return errors.New(fmt.Sprintf("Failed to open seeding file with error: %s", err.Error()))
	}

	defer func() {
		_ = f.Close()
	}()

	b, err := ioutil.ReadAll(f)
	if err != nil {
		return errors.New(fmt.Sprintf("Failed to read seeding file with error: %s", err.Error()))
	}

	dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=disable",
		cfg.Host, cfg.Username, cfg.Password, cfg.Database, cfg.Port)
	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
	if err != nil {
		return errors.New(fmt.Sprintf("Failed to connect to database with error: %s", err.Error()))
	}

	err = db.Exec(string(b)).Error
	if err != nil {
		return errors.New(fmt.Sprintf("Failed to execute seeding script: \n %s \n %s", path, err.Error()))
	}

	return nil
}
