package utility

import (
	"crawler/config"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"math/rand"
	"net"
	"net/http"
	"os"
	"regexp"
	"strings"
	"time"
)

// 패키지 호출 시 실행
func SetEnviron() {

	// 변수화 대상 설정 파일
	var configFiles = [...]string{
		config.ENV_PATH, config.CONFIG_PATH_DATABASE, config.CONFIG_PATH_MOVIE, config.CONFIG_PATH_G2A,
	}

	// json 설정 파일을 읽어들인 후 변수화
	for _, file := range configFiles {
		SetValueFromJsonFile(file)
	}

	// 현재 개발환경 변수 확인 (local, dev)
	var DEVELOPER_ENV = DeveloperEnv()
	if DEVELOPER_ENV == "" {
		DEVELOPER_ENV = config.Env.DeveloperEnv
	}

	if os.Setenv(config.EnvKey, DEVELOPER_ENV) == nil {
		config.Env.DeveloperEnv = DEVELOPER_ENV
	}
}

// json 파일에서 값 추출 후 변수화
func SetValueFromJsonFile(filePath string) {
	file, err := os.Open(filePath)
	defer func() {
		err := file.Close()
		if err != nil {
			log.Fatalln(err)
		}
	}()
	Check(err, nil)

	decoder := json.NewDecoder(file)

	switch filePath {
	case config.ENV_PATH:
		err = decoder.Decode(&config.Env)
	case config.CONFIG_PATH_DATABASE:
		err = decoder.Decode(&config.DB)
	case config.CONFIG_PATH_MOVIE:
		err = decoder.Decode(&config.Movie)
	case config.CONFIG_PATH_G2A:
		err = decoder.Decode(&config.G2A)
	}

	Check(err, nil)
}

// Gin 디버그 모드 사용할 것인가 여부
func SetDebug() {
	if config.Env.IsDebug {
		gin.SetMode(gin.DebugMode)
	} else {
		gin.SetMode(gin.ReleaseMode)
	}
}

// 오류 확인
func Check(err error, path interface{}) {
	if err != nil {
		if path == nil || path == "" {
			path = "./log/error.txt"
		}

		logFile, err := os.OpenFile(path.(string), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
		if err != nil {
			panic(err)
		}
		defer logFile.Close()

		logger := log.New(logFile, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
		logger.Printf("%s\n", err.Error())
	}
}

// 설정된 개발 환경변수 조회
func DeveloperEnv() string {
	return strings.ToLower(os.Getenv(config.EnvKey)) // 소문자로 변환
}

// 시작시간으로 부터 소요시간(초)
func GetDurationInMillseconds(start time.Time) float64 {
	end := time.Now()
	duration := end.Sub(start)
	milliseconds := float64(duration) / float64(time.Millisecond)
	rounded := float64(int(milliseconds*100+.5)) / 100
	return rounded
}

func IpAddrFromRemoteAddr(s string) string {
	idx := strings.LastIndex(s, ":")
	if idx == -1 {
		return s
	}
	return s[:idx]
}

// 현재 요청 IP
func GetClientIP(c *gin.Context) string {
	// first check the X-Forwarded-For header
	requester := c.Request.Header.Get("X-Forwarded-For")
	// if empty, check the Real-IP header
	if len(requester) == 0 {
		requester = c.Request.Header.Get("X-Real-IP")
	}
	// if the requester is still empty, use the hard-coded address from the socket
	if len(requester) == 0 {
		requester = c.Request.RemoteAddr
	}

	// if requester is a comma delimited list, take the first one
	// (this happens when proxied via elastic load balancer then again through nginx)
	if strings.Contains(requester, ",") {
		requester = strings.Split(requester, ",")[0]
	}

	return requester
}

// 현재 서버 IP
func GetLocalIP() string {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return ""
	}
	for _, address := range addrs {
		// check the address type and if it is not a loopback the display it
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				return ipnet.IP.String()
			}
		}
	}
	return ""
}

func RequestGetRemoteAddress(r *http.Request) string {
	hdr := r.Header
	hdrRealIP := hdr.Get("X-Real-Ip")
	hdrForwardedFor := hdr.Get("X-Forwarded-For")
	if hdrRealIP == "" && hdrForwardedFor == "" {
		return IpAddrFromRemoteAddr(r.RemoteAddr)
	}
	if hdrForwardedFor != "" {
		// X-Forwarded-For is potentially a list of addresses separated with ","
		parts := strings.Split(hdrForwardedFor, ",")
		for i, p := range parts {
			parts[i] = strings.TrimSpace(p)
		}
		// TODO: should return first non-local address
		return parts[0]
	}
	return hdrRealIP
}

// 사용자 useragent 조회
func UserAgent(w http.ResponseWriter, r *http.Request) {
	ua := r.UserAgent() //<---- simpler and faster!
	fmt.Printf("user agent is: %s \n", ua)
	w.Write([]byte("user agent is " + ua))
}

// 데이터 단위 변환
func ByteSize(bytes uint64) string {
	unit := ""
	value := float32(bytes)

	switch {
	case bytes >= config.TERABYTE:
		unit = "T"
		value = value / config.TERABYTE
	case bytes >= config.GIGABYTE:
		unit = "G"
		value = value / config.GIGABYTE
	case bytes >= config.MEGABYTE:
		unit = "M"
		value = value / config.MEGABYTE
	case bytes >= config.KILOBYTE:
		unit = "K"
		value = value / config.KILOBYTE
	case bytes >= config.BYTE:
		unit = "B"
	case bytes == 0:
		return "0"
	}

	stringValue := fmt.Sprintf("%.1f", value)
	stringValue = strings.TrimSuffix(stringValue, ".0")
	return fmt.Sprintf("%s%s", stringValue, unit)
}

// inArray
func Find(slice []string, val string) (int, bool) {
	for i, item := range slice {
		if item == val {
			return i, true
		}
	}
	return -1, false
}

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func RandomString() string {
	b := make([]byte, rand.Intn(10)+10)
	for i := range b {
		b[i] = letterBytes[rand.Intn(len(letterBytes))]
	}
	return string(b)
}

// 특수문자 제거
func RemoveSpecialChar(s string) string {
	return strings.TrimSpace(regexp.MustCompile(`[\{\}\[\]\/?.,;:|\)*~!^\-_+<>@\#$%&\\\=\(\'\"\n\r]+`).ReplaceAllString(s, ""))
}
