package helper

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"
	"unicode"

	"github.com/gookit/goutil/dump"
	"github.com/gorilla/mux"
)

// const (
// 	COLOR_Reset  = "\033[0m"
// 	COLOR_Red    = "\033[31m"
// 	COLOR_Green  = "\033[32m"
// 	COLOR_Yellow = "\033[33m"
// 	COLOR_Blue   = "\033[34m"
// 	COLOR_Purple = "\033[35m"
// 	COLOR_Cyan   = "\033[36m"
// 	COLOR_Gray   = "\033[37m"
// 	COLOR_White  = "\033[97m" // Brighter white
// )

// func Log(msg string) {
// 	log.New(os.Stdout, msg, log.Ldate|log.Ltime)

// }
// func Colorize(colorCode string, message string) string {
// 	return colorCode + message + COLOR_Reset
// }

type IsStruct struct{}

var (
	Is IsStruct
	h  = New()
)

type Helper struct {
	Convert Convert
	Struct  Struct
	Screen  Screen
	Error   Error
	If      If
	Log     Log
}
type Convert struct {
}
type Struct struct {
}
type Screen struct {
}
type Error struct {
	Val error
}
type If struct {
	Error Error
}
type Log struct {
	Error Error
}

func (h *Helper) P(val any) {
	dump.Println(val)
}

// InsertStringValueIntoField inserts a string value into a specified field of a struct.
//
// structPtr must be a pointer to a struct.
// fieldName is the name of the field (case-sensitive).
// stringValue is the string value to be inserted.
//
// Returns an error if:
//   - structPtr is not a pointer to a struct.
//   - The fieldName is not found in the struct.
//   - The field is not settable (e.g., unexported).
//   - The field is not of type string.
func New() *Helper {
	return &Helper{}
}

func (e *Error) Log() {
	if e.Val != nil {
		log.Println(e.Val.Error())
	}
}
func (e *Error) In(err error) *Error {
	e.Val = err
	return e
}
func (conv *Struct) Insert(structPtr interface{}, param ...any) {
	// func (conv *Struct) Insert(structPtr interface{}, fieldName string, stringValue string, sep string) string {

	rawFieldName := param[0].(string)
	fieldValue := param[1]
	sep := ""
	if len(param) > 2 {
		sep = param[2].(string)
	} else {
		sep = "_"
	}
	// if len(param[2].(string)) > 0 {
	// 	sep = param[2].(string)
	// }
	seperatorRune := []rune(sep)[0]
	field := CapitalizeAfterChar(rawFieldName, seperatorRune)
	field = CapitalizeFirstLetter(field)
	field = strings.ReplaceAll(field, sep, "")

	err := InsertAnyValueIntoField(structPtr, field, fieldValue)
	h.Error.Val = err
	h.If.Error.Log()
}
func (conv *Struct) ToStructField(str string, seperator string) string {
	seperatorRune := []rune(seperator)[0]
	field := CapitalizeAfterChar(str, seperatorRune)
	field = CapitalizeFirstLetter(field)
	field = strings.ReplaceAll(field, seperator, "")
	return field
}
func InsertStringValueIntoField(structPtr interface{}, fieldName string, stringValue string) error {
	if structPtr == nil {
		return errors.New("structPtr cannot be nil")
	}

	val := reflect.ValueOf(structPtr)
	if val.Kind() != reflect.Ptr {
		return errors.New("structPtr must be a pointer to a struct")
	}

	elem := val.Elem()
	if elem.Kind() != reflect.Struct {
		return errors.New("structPtr must be a pointer to a struct")
	}

	fieldVal := elem.FieldByName(fieldName)
	if !fieldVal.IsValid() {
		return fmt.Errorf("field '%s' not found in struct", fieldName)
	}

	if !fieldVal.CanSet() {
		return fmt.Errorf("field '%s' is not settable (unexported or embedded without being exported)", fieldName)
	}

	if fieldVal.Kind() != reflect.String {
		return fmt.Errorf("field '%s' is not a string type", fieldName)
	}

	fieldVal.SetString(stringValue)
	return nil
}
func InsertAnyValueIntoField(structPtr interface{}, fieldName string, anyValue any) error {
	if structPtr == nil {
		return errors.New("structPtr cannot be nil")
	}

	val := reflect.ValueOf(structPtr)
	if val.Kind() != reflect.Ptr {
		return errors.New("structPtr must be a pointer to a struct")
	}

	elem := val.Elem()
	if elem.Kind() != reflect.Struct {
		return errors.New("structPtr must be a pointer to a struct")
	}

	fieldVal := elem.FieldByName(fieldName)
	if !fieldVal.IsValid() {
		return fmt.Errorf("field '%s' not found in struct", fieldName)
	}

	if !fieldVal.CanSet() {
		return fmt.Errorf("field '%s' is not settable (unexported or embedded without being exported)", fieldName)
	}

	switch v := anyValue.(type) {
	case string:
		{
			if fieldVal.Kind() != reflect.String {
				return fmt.Errorf("field '%s' is not a string type", fieldName)
			}

			fieldVal.SetString(v)
			return nil
		}
	case bool:
		{
			if fieldVal.Kind() != reflect.Bool {
				return fmt.Errorf("field '%s' is not a string type", fieldName)
			}

			fieldVal.SetBool(v)
			return nil
		}
	}
	return fmt.Errorf("value of field '%s' is unknown type", fieldName)
}

// CapitalizeAfterChar capitalizes the first letter immediately following each occurrence of a specific character in a string.
//
// For example:
// CapitalizeAfterChar("hello_world", '_') == "hello_World"
// CapitalizeAfterChar("this-is-a-test", '-') == "this-Is-A-Test"
// CapitalizeAfterChar("  leading spaces and_underscores", '_') == "  leading spaces and_Underscores"
func CapitalizeAfterChar(input string, char rune) string {
	var result strings.Builder
	capitalizeNext := false // Flag to indicate if the next letter should be capitalized

	for _, r := range input {
		if capitalizeNext {
			result.WriteRune(unicode.ToUpper(r)) // Capitalize the current rune
			capitalizeNext = false               // Reset the flag
		} else {
			result.WriteRune(r) // Write the rune as is
		}

		if r == char {
			capitalizeNext = true // Set the flag to capitalize the next letter
		}
	}
	return result.String()
}

func CapitalizeAfterCharMulti(input string, char rune) string {
	var result strings.Builder
	capitalizeNext := false // Flag to indicate if the next letter should be capitalized

	for _, r := range input {
		if capitalizeNext {
			result.WriteRune(unicode.ToUpper(r)) // Capitalize the current rune
			capitalizeNext = false               // Reset the flag
		} else {
			result.WriteRune(r) // Write the rune as is
		}

		if r == char {
			capitalizeNext = true // Set the flag to capitalize the next letter
		}
	}
	return result.String()
}

func CapitalizeFirstLetter(input string) string {
	if input == "" {
		return input // Return empty string if input is empty
	}

	runes := []rune(input) // Convert string to rune slice for Unicode support
	firstRune := runes[0]

	if !unicode.IsLetter(firstRune) {
		return input // Return original string if first char is not a letter
	}

	capitalizedFirstRune := unicode.ToUpper(firstRune)
	runes[0] = capitalizedFirstRune

	return string(runes) // Convert rune slice back to string
}
func MapIter[K string, V any](m map[K]V, fn func(K, V)) {
	for p, d := range m {
		fn(p, d)
	}
}
func (*Screen) Clear() {
	if runtime.GOOS == "windows" {
		cmd := exec.Command("cmd", "/c", "cls")
		cmd.Stdout = os.Stdout
		cmd.Run()
	} else {
		cmd := exec.Command("clear")
		cmd.Stdout = os.Stdout
		cmd.Run()
	}
}

func RandStringByBits(nBits int) string {
	b := make([]byte, nBits/8)
	if _, err := io.ReadFull(rand.Reader, b); err != nil {
		return err.Error()
	}
	return base64.RawURLEncoding.EncodeToString(b)
}

func StructToMap(obj interface{}) map[string]any {

	val := reflect.ValueOf(obj)
	typ := reflect.TypeOf(obj)
	result := make(map[string]any)

	for i := 0; i < val.NumField(); i++ {
		field := typ.Field(i)
		result[field.Name] = val.Field(i).Interface()
	}

	return result
}

func FetchGetJson(u string) (map[string]any, error) {
	resp, err := http.Get(u)
	if err != nil {
		return nil, err
	}
	respByes, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	data := map[string]any{}
	json.Unmarshal(respByes, &data)
	return data, nil
}

func GetLastChar(str string) string {
	return str[len(str)-1:]
}

func IsLastChar(str string, chr string) bool {
	if l := GetLastChar(str); l == chr {
		return true
	}
	return false

}

func IsLastCharDo(str string, chr string, f func(r bool)) bool {
	result := IsLastChar(str, chr)

	func(bool) {
		f(result)
	}(result)

	return result
}

func IsFetchOK[V any](u string, funcName string, f func(string) (*http.Response, error)) *http.Response {
	// errCntr := 0
	errCntrTotal := 0
	var (
		a    V
		resp *http.Response
		err  error
	)
	for {
		resp, err = f(u)
		if err == nil && resp.StatusCode == 200 {
			// ClearScreen()
			break
		} else {
			// errCntr++
			// errCntrTotal++
			// if errCntr == 10 {
			// 	errCntr = 0
			// 	ClearScreen()
			// }
			// if errCntr == 1 {
			// 	ClearScreen()
			// }
			errCntrTotalStr := strconv.Itoa(errCntrTotal)
			pkg := reflect.TypeOf(a).PkgPath()
			dt := time.Now()
			dateTime := dt.Format("02-01-2006 15:04:05")
			fmt.Println("ERROR: " + dateTime + ">---------<" + errCntrTotalStr + ">")
			fmt.Println("        :")
			fmt.Println(" Package name: " + pkg)
			fmt.Println("Function name: " + funcName)
			fmt.Println("          URL: " + u)
			fmt.Println("Error message: " + err.Error())
			fmt.Println("--------------------------------------")
			time.Sleep(5 * time.Second)
		}

	}
	return resp
}

func IsSliceContains[T comparable](sliceVals []T, valueToSearch T) bool {
	for _, v := range sliceVals {
		if v == valueToSearch {
			return true
		}
	}
	return false
}

func AddDotBetween(k ...string) string {
	constructedKey := ""
	for i, key := range k {
		constructedKey += key
		if i < len(k)-1 {
			constructedKey += "."
		}
	}
	return constructedKey
}

func RemoveSliceDuplicates(elements []string) []string {
	encountered := map[string]bool{}
	result := []string{}

	for v := range elements {
		if encountered[elements[v]] {
			// Do not add duplicate.
		} else {
			// Record this element as an encountered element.
			encountered[elements[v]] = true
			// Append to result slice.
			result = append(result, elements[v])
		}
	}
	// Return the new slice.
	return result
}

func IfLast[T any](counter int, someVar map[string]T, fn func()) {
	counter++
	if len(someVar) == counter {
		fn()
	}
}

func (s *IsStruct) Pointer(v any) bool {

	valueOfP := reflect.ValueOf(v)
	if valueOfP.Kind() == reflect.Ptr {
		return true
	} else {
		return false
	}
}

func AppRunner(runApp func()) {
	wg := sync.WaitGroup{}
	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stop()

	runApp()

	<-ctx.Done()
	//do stuff after ending
	wg.Wait()
	log.Println("AppRunner stopped")
	os.Exit(0)
}

func StartTestHTTPServer(port int, name string) {
	p := strconv.Itoa(port)
	go func() {
		log.Println("Test server is running at http://" + GetIP() + ":" + p)
		r := mux.NewRouter()
		r.Path("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			fmt.Fprintln(w, r.Host+"/ OK")
		})
		r.Path("/test1").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			fmt.Fprintln(w, r.Host+"/test1 OK")
		})
		r.Path("/test2").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			fmt.Fprintln(w, r.Host+"/test2 OK")
		})
		err := http.ListenAndServe(":"+p, r)
		if err != nil {
			log.Println(err.Error())
		}
	}()
}

func GetIP(prefix ...string) string {
	prfx := "192.168."
	if len(prefix) > 0 {
		prfx = prefix[0]
	}

	addrs, err := net.InterfaceAddrs()
	if err != nil {
		fmt.Println("Error getting interface addresses:", err)
		return ""
	}

	for _, addr := range addrs {
		// Check if the address is an IP address
		if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {

			a := strings.HasPrefix(ipnet.IP.String(), prfx)
			c := ipnet.IP.To4() != nil
			if c && a { //check for ipv4
				prfx = ipnet.IP.String()
				break
			}
			// } else if ipnet.IP.To16() != nil { //check for ipv6
			// 	fmt.Println("Local IPv6 address:", ipnet.IP.String())
			// }
		}
	}
	return prfx
}

func AppendToPointer[T any](slc *[]T, val T) {
	*slc = append(*slc, val)
}
