// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.

package server

import (
	"fmt"
	"os"
	"path/filepath"
	"unicode"

	"github.com/harness/gitness/events"
	"github.com/harness/gitness/gitrpc"
	"github.com/harness/gitness/gitrpc/server"
	"github.com/harness/gitness/internal/services/webhook"
	"github.com/harness/gitness/types"

	"github.com/kelseyhightower/envconfig"
	"golang.org/x/text/runes"
	"golang.org/x/text/transform"
	"golang.org/x/text/unicode/norm"
)

// LoadConfig returns the system configuration from the
// host environment.
func LoadConfig() (*types.Config, error) {
	config := new(types.Config)
	err := envconfig.Process("", config)
	if err != nil {
		return nil, err
	}

	config.InstanceID, err = getSanitizedMachineName()
	if err != nil {
		return nil, fmt.Errorf("unable to ensure that instance ID is set in config: %w", err)
	}

	return config, nil
}

// getSanitizedMachineName gets the name of the machine and returns it in sanitized format.
func getSanitizedMachineName() (string, error) {
	// use the hostname as default id of the instance
	hostName, err := os.Hostname()
	if err != nil {
		return "", err
	}

	// Always cast to lower and remove all unwanted chars
	// NOTE: this could theoretically lead to overlaps, then it should be passed explicitly
	// NOTE: for k8s names/ids below modifications are all noops
	// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/

	// The following code will:
	// * remove invalid runes
	// * remove diacritical marks (ie "smörgåsbord" to "smorgasbord")
	// * lowercase A-Z to a-z
	// * leave only a-z, 0-9, '-', '.' and replace everything else with '_'
	hostName, _, err = transform.String(
		transform.Chain(
			norm.NFD,
			runes.ReplaceIllFormed(),
			runes.Remove(runes.In(unicode.Mn)),
			runes.Map(func(r rune) rune {
				switch {
				case 'A' <= r && r <= 'Z':
					return r + 32
				case 'a' <= r && r <= 'z':
					return r
				case '0' <= r && r <= '9':
					return r
				case r == '-', r == '.':
					return r
				default:
					return '_'
				}
			}),
			norm.NFC),
		hostName)
	if err != nil {
		return "", err
	}

	return hostName, nil
}

// ProvideGitRPCServerConfig loads the gitrpc server config from the environment.
// It backfills certain config elements to work with cmdone.
func ProvideGitRPCServerConfig() (server.Config, error) {
	config := server.Config{}
	err := envconfig.Process("", &config)
	if err != nil {
		return server.Config{}, fmt.Errorf("failed to load gitrpc server config: %w", err)
	}
	if config.GitHookPath == "" {
		var executablePath string
		executablePath, err = os.Executable()
		if err != nil {
			return server.Config{}, fmt.Errorf("failed to get path of current executable: %w", err)
		}

		config.GitHookPath = executablePath
	}
	if config.GitRoot == "" {
		var homedir string
		homedir, err = os.UserHomeDir()
		if err != nil {
			return server.Config{}, err
		}

		config.GitRoot = filepath.Join(homedir, ".gitrpc")
	}

	return config, nil
}

// ProvideGitRPCClientConfig loads the gitrpc client config from the environment.
func ProvideGitRPCClientConfig() (gitrpc.Config, error) {
	config := gitrpc.Config{}
	err := envconfig.Process("", &config)
	if err != nil {
		return gitrpc.Config{}, fmt.Errorf("failed to load gitrpc client config: %w", err)
	}

	return config, nil
}

// ProvideEventsConfig loads the events config from the environment.
func ProvideEventsConfig() (events.Config, error) {
	config := events.Config{}
	err := envconfig.Process("", &config)
	if err != nil {
		return events.Config{}, fmt.Errorf("failed to load events config: %w", err)
	}

	return config, nil
}

// ProvideWebhookConfig loads the webhook config from the environment.
// It backfills certain config elements if required.
func ProvideWebhookConfig() (webhook.Config, error) {
	config := webhook.Config{}
	err := envconfig.Process("", &config)
	if err != nil {
		return webhook.Config{}, fmt.Errorf("failed to load events config: %w", err)
	}

	if config.EventReaderName == "" {
		config.EventReaderName, err = getSanitizedMachineName()
		if err != nil {
			return webhook.Config{}, fmt.Errorf("failed to get sanitized machine name: %w", err)
		}
	}

	return config, nil
}
