package util

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"net/url"
	"os"
	"os/user"
	"strings"
	"text/template"

	"github.com/Masterminds/sprig/v3"
	"golang.org/x/text/cases"
	"golang.org/x/text/language"
)

const HOST_COLLECTORS_RUN_AS_ROOT_PROMPT = "Some host collectors need to be run as root.\nDo you want to exit and rerun the command using sudo?"

func HomeDir() string {
	if h := os.Getenv("HOME"); h != "" {
		return h
	}
	return ""
}

func IsURL(str string) bool {
	parsed, err := url.ParseRequestURI(str)
	if err != nil {
		return false
	}

	return parsed.Scheme != ""
}

func AppName(name string) string {
	words := strings.Split(cases.Title(language.English).String(strings.ReplaceAll(name, "-", " ")), " ")
	casedWords := []string{}
	for i, word := range words {
		if strings.ToLower(word) == "ai" {
			casedWords = append(casedWords, "AI")
		} else if strings.ToLower(word) == "io" && i > 0 {
			casedWords[i-1] += ".io"
		} else {
			casedWords = append(casedWords, word)
		}
	}

	return strings.Join(casedWords, " ")
}

func SplitYAML(doc string) []string {
	return strings.Split(doc, "\n---\n")
}

func EstimateNumberOfLines(text string) int {
	n := strings.Count(text, "\n")
	if len(text) > 0 && !strings.HasSuffix(text, "\n") {
		n++
	}
	return n
}

// Append appends elements in src to target.
// We have this function because of how the
// builtin append() function works. It treats
// target nil slices the same as empty slices.
func Append[T any](target []T, src []T) []T {
	// Do nothing only if src is nil
	if src == nil {
		return target
	}

	// In case target is nil, we need to initialize it
	// since append() will not do it for us when len(src) == 0
	if target == nil {
		target = []T{}
	}
	return append(target, src...)
}

// IsInCluster returns true if the code is running within a process
// inside a kubernetes pod
func IsInCluster() bool {
	// This is a best effort check, it's not guaranteed to be accurate
	host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
	if len(host) == 0 || len(port) == 0 {
		return false
	}

	return true
}

// RenderTemplate renders a template and returns the result as a string
func RenderTemplate(tpl string, data interface{}) (string, error) {
	// Create a new template and parse the letter into it
	t, err := template.New("data").Funcs(sprig.FuncMap()).Parse(tpl)
	if err != nil {
		return "", err
	}

	// Create a new buffer
	buf := new(bytes.Buffer)

	// Execute the template and write the bytes to the buffer
	err = t.Execute(buf, data)
	if err != nil {
		return "", err
	}

	// Return the string representation of the buffer
	return buf.String(), nil
}

func IsRunningAsRoot() bool {
	currentUser, err := user.Current()
	if err != nil {
		return false
	}

	// Check if the user ID is 0 (root's UID)
	return currentUser.Uid == "0"
}

func PromptYesNo(question string) bool {
	reader := bufio.NewReader(os.Stdin)

	for {
		fmt.Printf("%s (yes/no): ", question)

		response, err := reader.ReadString('\n')
		if err != nil {
			fmt.Println("Error reading response:", err)
			continue
		}

		response = strings.TrimSpace(response)
		response = strings.ToLower(response)

		if response == "yes" || response == "y" {
			return true
		} else if response == "no" || response == "n" {
			return false
		} else {
			fmt.Println("Please type 'yes' or 'no'.")
		}
	}
}

func Dedup[T any](objs []T) []T {
	seen := make(map[string]bool)
	out := []T{}

	if len(objs) == 0 {
		return objs
	}

	for _, o := range objs {
		data, err := json.Marshal(o)
		if err != nil {
			out = append(out, o)
			continue
		}
		key := string(data)
		if _, ok := seen[key]; !ok {
			out = append(out, o)
			seen[key] = true
		}
	}
	return out
}
