package strutil

import (
	"fmt"
	"hash/crc32"
	"math/rand" // TODO use v2 on 1.22+
	"os"
	"strconv"
	"sync"
	"sync/atomic"
	"time"

	"github.com/gookit/goutil/x/basefn"
)

// global id:
//
//	https://github.com/rs/xid
//	https://github.com/satori/go.uuid
var (
	DefMinInt = 1000
	DefMaxInt = 9999
)

// MicroTimeID generate.
//   - return like: 16074145697981929446(len: 20)
//
// Conv Base:
//
//	mtId := MicroTimeID() // eg: 16935349145643425047 len: 20
//	b16id := Base10Conv(mtId, 16) // eg: eb067252154a9d17 len: 16
//	b32id := Base10Conv(mtId, 32) // eg: em1jia8akl78n len: 13
//	b36id := Base10Conv(mtId, 36) // eg: 3ko088phiuoev len: 13
//	b62id := Base10Conv(mtId, 62) // eg: kb24SKgsQ9V len: 11
func MicroTimeID() string { return MTimeBaseID(10) }

// MicroTimeHexID micro time HEX ID generate.
//
// return like: 643d4cec7db9e(len: 13)
func MicroTimeHexID() string { return MTimeHexID() }

// MTimeHexID micro time HEX ID generate.
//
// return like: 643d4cec7db9e(len: 13)
func MTimeHexID() string { return MTimeBaseID(16) }

// MTimeBase36 micro time BASE36 id generate.
func MTimeBase36() string { return MTimeBaseID(36) }

// MTimeBaseID micro time BASE id generate. toBase: 2-36
//
// Examples:
//   - toBase=16: 643d4cec7db9e(len: 13)
//   - toBase=36: hd312z9ka2(len: 10)
func MTimeBaseID(toBase int) string {
	// eg: 1763431181849557
	ms := time.Now().UnixMicro()
	// rand 1000 - 9999
	// ri := mathutil.RandomInt(DefMinInt, DefMaxInt)
	ri := 1000 + rand.Int63n(8999)

	if toBase > 36 {
		return BaseConvInt(uint64(ms)+uint64(ri), toBase)
	}
	return strconv.FormatInt(ms+ri, toBase)
}

// DatetimeNo generate. can use for order-no.
//
//   - No prefix, return like: 2023041410484904074285478388(len: 28)
//   - With prefix, return like: prefix2023041410484904074285478388(len: 28 + len(prefix))
func DatetimeNo(prefix string) string { return DateSN(prefix) }

// DateSN generate date serial number. PREFIX + yyyyMMddHHmmss + ext(微秒+随机数)
func DateSN(prefix string) string {
	nt := time.Now()
	pl := len(prefix)
	bs := make([]byte, 0, 28+pl)
	if pl > 0 {
		bs = append(bs, prefix...)
	}

	// micro datetime
	bs = nt.AppendFormat(bs, "20060102150405.000000")
	bs[14+pl] = '0'

	// host
	name, err := os.Hostname()
	if err != nil {
		name = "default"
	}
	c32 := crc32.ChecksumIEEE([]byte(name)) // eg: 4006367001
	bs = strconv.AppendUint(bs, uint64(c32%99), 10)

	// rand 1000 - 9999
	// rs := rand.New(rand.NewSource(nt.UnixNano()))
	bs = strconv.AppendInt(bs, 1000+rand.Int63n(8999), 10)

	return string(bs)
}

// DateSNOpt 基于时间生成唯一编号
type DateSNOpt struct {
	Layout string // time layout
	// RandMax   int    // rand max
	DateLen   int   // 时间格式长度，后面部分将会进行进制转换 默认 8(yyyyMMdd)
	ConvBase  int   // DateLen 之后的转换 base 2-64. default 36
	// EnableSeq bool  // 需要高并发生成时可以启用自增序号。默认不启用
	SeqMaxVal int   // 自增序号最大值，之后后自动重置
	globalSeq int64 // 自增，确保同一时刻生成的编号不重复. EnableSeq=true 时启用
}

// default setting: {时间年到秒14位}
var defOpt = NewDateSNOpt()

// ConfigSNOpt config default date sn option
func ConfigSNOpt(fn func(opt *DateSNOpt)) {
	fn(defOpt)
}

// NewDateSNOpt create a new DateSNOpt instance.
func NewDateSNOpt() *DateSNOpt {
	return &DateSNOpt{
		Layout: "20060102150405.000000",
		// RandMax:   8999,
		DateLen:   8,
		ConvBase:  36,
		// EnableSeq: true,
		SeqMaxVal: 8999,
	}
}

// prepare for generate
func (do *DateSNOpt) prepare() {
	if do.DateLen <= 0 {
		do.DateLen = 8 // default 8 for yyyyMMdd
	}
	if do.ConvBase <= 0 {
		do.ConvBase = 36
	}
	if do.Layout == "" {
		do.Layout = "20060102150405.000000"
	}
	// get sequence max value (default 9999)
	if do.SeqMaxVal <= 0 {
		do.SeqMaxVal = 8999
	}
}

func (do *DateSNOpt) getSeqValue() int64 {
	// use atomic sequence for guaranteed uniqueness (even without EnableSeq)
	// this ensures no collisions in tight loops
	seq := atomic.AddInt64(&do.globalSeq, 1)

	// auto reset when seq exceeds SeqMaxVal (thread-safe using CAS)
	if seq > int64(do.SeqMaxVal) {
		// try to reset to 1, other goroutines may have already done it
		atomic.CompareAndSwapInt64(&do.globalSeq, seq, 1)
		seq = seq % int64(do.SeqMaxVal)
		if seq == 0 {
			seq = 1
		}
	}
	return 1000 + seq
}

// GenSN generate date serial number.
func (do *DateSNOpt) GenSN(prefix string) string {
	pl := len(prefix)
	bs := make([]byte, 0, 22+pl)
	if pl > 0 {
		bs = append(bs, prefix...)
	}
	do.prepare()

	// get time and format
	nt := time.Now()
	bs = nt.AppendFormat(bs, do.Layout)

	// remove the dot separator if exists
	dotIdx := -1
	for i := pl; i < len(bs); i++ {
		if bs[i] == '.' {
			dotIdx = i
			break
		}
	}
	if dotIdx > 0 {
		bs = append(bs[:dotIdx], bs[dotIdx+1:]...)
	}

	// determine date length (default 8 for yyyyMMdd)
	idx := do.DateLen + pl
	// high concurrency mode: sequence is the main differentiator
	extBs := strconv.AppendInt(bs[idx:], do.getSeqValue(), 10)
	if extBs[0] == '0' {
		extBs[0] = '1'
	}
	extInt := SafeUint(string(extBs))

	// convert extension to target base
	bs = append(bs[:idx], BaseConvInt(extInt, do.ConvBase)...)
	return string(bs)
}

// 确保同一时刻生成的编号不重复 max: 89999
var globalSeqSnV2 int64 = 0

func getSeqValue() int64 {
	// use atomic sequence for guaranteed uniqueness (even without EnableSeq)
	// this ensures no collisions in tight loops
	seq := atomic.AddInt64(&globalSeqSnV2, 1)

	// auto reset when seq exceeds SeqMaxVal (thread-safe using CAS)
	if seq > 89999 {
		// try to reset to 1, other goroutines may have already done it
		atomic.CompareAndSwapInt64(&globalSeqSnV2, seq, 1)
		seq = seq % 89999
		if seq == 0 {
			seq = 1
		}
	}
	return seq
}

// DateSNv2 generate date serial number.
//   - 2 < extBase <= 36
//   - return: PREFIX + yyyyMMddHHmmss + extBase(6bit micro + 5bit random number)
//
// Example:
//   - prefix=P, extBase=16, return: P2023091414361354b4490(len=22)
//   - prefix=P, extBase=36, return: P202309141436131gw3jg(len=21)
func DateSNv2(prefix string, extBase ...int) string {
	pl := len(prefix)
	bs := make([]byte, 0, 22+pl)
	if pl > 0 {
		bs = append(bs, prefix...)
	}

	// micro datetime
	nt := time.Now()
	bs = nt.AppendFormat(bs, "20060102150405.000000")

	// 6bit micro + 5bit rand 10000 - 99999
	// ext := strconv.AppendInt(bs[16+pl:], 10000+rand.Int63n(89999), 10)
	ext := strconv.AppendInt(bs[16+pl:], 10000+getSeqValue(), 10)
	// prefix + yyyyMMddHHmmss + ext(convert to base)
	base := basefn.FirstOr(extBase, 36)
	bs = append(bs[:14+pl], strconv.FormatInt(SafeInt64(string(ext)), base)...)

	return string(bs)
}

var (
	// key is dateLen + extBase
	dsoMap   = make(map[string]*DateSNOpt)
	dsoMutex sync.RWMutex
)

// DateSNv3 generate date serial number.
//   - 2 < extBase <= 64
//   - return: PREFIX + DATETIME(yyyyMMddHHmmss).dateLen + extBase(DATETIME.after+6bit micro + 5bit random number)
//   - dateLen: 为 DATETIME(yyyyMMddHHmmss) 保留的长度，默认为 8(yyyyMMdd) 后面的给 extBase 使用
//
// Example:
//   - prefix=P, dateLen=8, extBase=16, return: P202511139vs99gbifnj len: 20
//   - prefix=P, dateLen=6, extBase=36, return: P2025119yn52qhefati len: 19
//   - prefix=P, dateLen=6, extBase=48, return: P202511k9ksgD1fe6x len: 18
//   - prefix=P, dateLen=4, extBase=62, return: P2025aZl8N0y58M7 len: 16
func DateSNv3(prefix string, dateLen int, extBase ...int) string {
	baseVal := basefn.FirstOr(extBase, 36)
	cacheKey := fmt.Sprintf("%d%d", dateLen, baseVal)

	dsoMutex.RLock()
	dso, ok := dsoMap[cacheKey]
	dsoMutex.RUnlock()

	if !ok {
		dsoMutex.Lock()
		// double check，防止在加锁期间其他 goroutine 已经创建
		if dso, ok = dsoMap[cacheKey]; !ok {
			dso = NewDateSNOpt()
			// dso.EnableSeq = true
			dso.DateLen = dateLen
			dso.ConvBase = baseVal
			dsoMap[cacheKey] = dso
		}
		dsoMutex.Unlock()
	}
	return dso.GenSN(prefix)
}
