package tools

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"html"
	"io"
	"net"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
)

var (
	snowflakeMutex     sync.Mutex
	snowflakeMachineID int64
	snowflakeSequence  int64
	snowflakeTimestamp int64

	// Updates to normalizers here should be mirrored in `GET_Limits.go`!

	RegexSpaces         = regexp.MustCompile(`\s{2,}`)
	RegexUnderscores    = regexp.MustCompile(`_+`)
	RegexNewlines       = regexp.MustCompile(`\n{2,}`)
	RegexMatcherTitle   = regexp.MustCompile(`^[\S\s]{1,80}$`)
	RegexMatcherTag     = regexp.MustCompile(`^[\p{L}\p{N}_]{1,32}$`)
	RegexMatcherComment = regexp.MustCompile(`^[\S\s]{10,240}$`)
)

func NormalizeTitle(str string) (string, bool) {
	if str == "" {
		return str, false
	}
	if !RegexMatcherTitle.MatchString(str) {
		return str, false
	}
	str = RegexSpaces.ReplaceAllString(str, " ")
	str = strings.TrimSpace(str)
	str = html.EscapeString(str)
	return str, true
}

func NormalizeTag(str string) (string, bool) {
	if str == "" {
		return str, false
	}
	if !RegexMatcherTag.MatchString(str) {
		return str, false
	}
	str = RegexUnderscores.ReplaceAllString(str, "_")
	str = strings.Trim(str, "_")
	str = strings.ToUpper(str)
	return str, true
}

func NormalizeComment(str string) (string, bool) {
	if str == "" {
		return str, false
	}
	if !RegexMatcherComment.MatchString(str) {
		return str, false
	}
	str = RegexNewlines.ReplaceAllString(str, " ")
	str = strings.TrimSpace(str)
	str = html.EscapeString(str)
	return str, true
}

func ParseLimit(str string) int {
	v, _ := strconv.Atoi(str)
	return min(100, max(1, v))
}

func ParseSnowflake(str string) int64 {
	v, err := strconv.ParseInt(str, 10, 64)
	if err != nil || v < 1 {
		return 0
	}
	return v
}

func ParseJSON(r io.Reader, v any) error {
	l := io.LimitReader(r, int64(LIMIT_JSON))
	d := json.NewDecoder(l)
	d.DisallowUnknownFields()
	return d.Decode(v)
}

// Generate a Unique Snowflake
func RequestSnowflake() int64 {
	snowflakeMutex.Lock()
	defer snowflakeMutex.Unlock()

	now := time.Now().UnixMilli()

	if now != snowflakeTimestamp {
		snowflakeSequence = 0
	} else {
		snowflakeSequence++
		if snowflakeSequence > SNOWFLAKE_MAX_SEQUENCE {
			for now <= snowflakeTimestamp {
				time.Sleep(time.Millisecond)
				now = time.Now().UnixMilli()
			}
			snowflakeSequence = 0
		}
	}

	snowflakeTimestamp = now
	return ((now - SNOWFLAKE_EPOCH_MILLI) << 22) | (snowflakeMachineID << 12) | snowflakeSequence
}

// Generate a Hex String out of random bytes
func RequestToken() string {
	b := make([]byte, 32)
	if _, err := io.ReadFull(rand.Reader, b); err != nil {
		panic("failed to generate enough random bytes")
	}
	return hex.EncodeToString(b)
}

// Generate a SHA256 Hex String from a given string
func RequestHash(str string) string {
	h := sha256.Sum256([]byte(str))
	return hex.EncodeToString(h[:])
}

// Get the Client IP Address as a SHA256 Hex String
func RequestAddressHash(r *http.Request) string {
	return RequestHash(RequestAddress(r))
}

// Get the Client IP Address
func RequestAddress(r *http.Request) string {
	if HTTP_PROXY != "" {
		return r.Header.Get(HTTP_PROXY)
	}
	addr, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		return ""
	}
	return addr
}
