package utils

import (
	"context"
	"log"
	"log/slog"
	"net"
	"os"
	"path/filepath"
	"reflect"
	"time"
)

const rsaBits = 4096

func GetFilePath(path string) string {
	// Check for empty filename
	if len(path) == 0 {
		return ""
	}

	// check if filename exists
	if _, err := os.Stat(path); os.IsNotExist((err)) {
		slog.Info("File not found, searching in same directory as binary", "filename", path)
		// if not, check that it exists in the same directory as the currently executing binary
		ex, err2 := os.Executable()
		if err2 != nil {
			slog.Error("Error determining binary path", "error", err)
			return ""
		}
		binaryPath := filepath.Dir(ex)
		path = filepath.Join(binaryPath, path)
	}
	return path
}

// Get preferred outbound ip of this machine
// @see https://stackoverflow.com/questions/23558425/how-do-i-get-the-local-ip-address-in-go
func GetOutboundIP() net.IP {
	conn, err := net.Dial("udp", "8.8.8.8:80")
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	localAddr := conn.LocalAddr().(*net.UDPAddr)

	return localAddr.IP
}

// Check if a file exists from https://stackoverflow.com/questions/12518876/how-to-check-if-a-file-exists-in-go
func FileExists(filename string) bool {
	info, err := os.Stat(filename)
	if os.IsNotExist(err) {
		return false
	}
	return !info.IsDir()
}

func SleepWithContext(ctx context.Context, d time.Duration) {
	timer := time.NewTimer(d)
	select {
	case <-ctx.Done():
		if !timer.Stop() {
			<-timer.C
		}
	case <-timer.C:
	}
}

// Generic converter using reflection
func ConvertStruct(src interface{}, dst interface{}) {
	srcVal := reflect.ValueOf(src)
	srcType := reflect.TypeOf(src)
	dstVal := reflect.ValueOf(dst).Elem()

	for i := 0; i < srcVal.NumField(); i++ {
		srcField := srcVal.Field(i)
		dstField := dstVal.FieldByName(srcType.Field(i).Name)

		if !dstField.IsValid() || !dstField.CanSet() {
			continue
		}

		switch srcField.Type().Name() {
		case "NullString":
			if srcField.FieldByName("Valid").Bool() {
				dstField.SetString(srcField.FieldByName("String").String())
			} else {
				dstField.SetString("")
			}
		case "NullTime":
			if srcField.FieldByName("Valid").Bool() {
				t := srcField.FieldByName("Time").Interface().(time.Time)
				dstField.SetString(t.Format("2006-01-02 15:04:05"))
			} else {
				dstField.SetString("")
			}
		default:
			// Handle int64 -> int conversion
			if srcField.Kind() == reflect.Int64 && dstField.Kind() == reflect.Int {
				dstField.SetInt(srcField.Int())
			}
		}
	}
}
