package configs

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"path"
)

const (
	ConfigFileName string = ".devtool"
)

var (
	ErrConfigNotInitialized error = errors.New("Config file is not initialized")
)

type Config struct {
	// ProjectPath is the location on your host where all dev relevant folders will be stored to
	ProjectPath string
	// VolumePath is where all the persisitant data will be stored
	VolumePath string
	// Credentials for UI login
	LoginCredential LoginCredential
	// key is repository name, for example, "repository-ee"
	RepositoryConfig map[string]RepositoryConfig
}

// LoginCredential stores the user credential for API request
type LoginCredential struct {
	Username string
	Password string
	Address  string
}

type RepositoryConfig struct {
	Name        string
	URL         string
	Directory   string
	Private     bool
	GitUsername string
	GitPassword string
}

func GetConfig() (*Config, error) {
	file, err := getConfigFile(ConfigFileName)
	if err != nil {
		if err == ErrConfigNotInitialized {
			return initializeConfig(file)
		}
		return nil, err
	}

	defer file.Close()

	return getConfig(file)
}

func (config *Config) Summarize() {
	fmt.Printf("The project path is %s\nThe volume path is %s\n", config.ProjectPath, config.VolumePath)
	if config.LoginCredential.Username != "" && config.LoginCredential.Password != "" {
		fmt.Printf("Login credential [%s] is configured\n", config.LoginCredential.Username)
	}

	if len(config.RepositoryConfig) > 0 {
		for name := range config.RepositoryConfig {
			fmt.Printf("Repository [%s] is added\n", name)
		}
	} else {
		fmt.Println("No repository is added")
	}
}

// initializeConfig will set up the mandatory dev information for the first time.
// such as devtool path, login credential
// The configuration also can be updated later
func initializeConfig(w io.WriteCloser) (*Config, error) {
	config := &Config{}
	fmt.Printf("Initialize devtool path:\n (Project path will store volumes and repositories)")
	fmt.Scanf("%s", &(config.ProjectPath))

	// generate volume path automatically
	config.VolumePath = path.Join(config.ProjectPath, "volumes")

	var loginCredential LoginCredential
	fmt.Printf("Set login credential username(admin): ")
	fmt.Scanf("%s", &(loginCredential.Username))
	if loginCredential.Username == "" {
		loginCredential.Username = "admin"
	}

	for {
		fmt.Printf("Set login credential password(******): ")
		fmt.Scanf("%s", &(loginCredential.Password))
		if loginCredential.Password != "" {
			break
		}

		fmt.Println("Login credential password must be provided")
	}

	fmt.Printf("Set login address(127.0.0.1): ")
	fmt.Scanf("%s", &(loginCredential.Address))
	if loginCredential.Address == "" {
		loginCredential.Address = "http://127.0.0.1:9000/api/auth"
	} else {
		loginCredential.Address = fmt.Sprintf("http://%s:9000/api/auth", loginCredential.Address)
	}

	config.LoginCredential = loginCredential

	// able to configure multiple project
	// if utils.PromptConfirm("Do you want to configure the repository now?") {
	// 	// configure repository
	// }

	bytes, err := json.Marshal(config)
	if err != nil {
		return nil, err
	}

	_, err = w.Write(bytes)
	if err != nil {
		return nil, err
	}
	return config, nil
}

func getConfig(f *os.File) (*Config, error) {
	config := &Config{}

	info, err := f.Stat()
	if err != nil {
		return nil, err
	}
	bytes := make([]byte, info.Size())
	n, err := f.Read(bytes)
	if err != nil {
		return nil, err
	}

	if n == 0 {
		// The file exists, but it's empty file, so we need to initalize
		return initializeConfig(f)
	}

	err = json.Unmarshal(bytes, &config)
	if err != nil {
		return nil, err
	}
	return config, nil
}

// getConfigFile get the config file handler
func getConfigFile(name string) (*os.File, error) {
	_, err := os.Stat(name)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			//create file
			file, err := os.Create(name)
			if err != nil {
				return nil, fmt.Errorf("fail to create config file: %w", err)
			}

			// first set up the git project path and volume path
			// git credential

			return file, err
		} else {
			return nil, err
		}
	}
	return os.OpenFile(name, os.O_RDWR, 0644)
}
