package models

import (
	"crypto/rand"
	"errors"
	"fmt"
	"github.com/go-playground/validator/v10"
	"github.com/rs/zerolog/log"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"gogs.mikescher.com/BlackForestBytes/goext/rext"
	"math/big"
	"reflect"
	"regexp"
	"strings"
)

type EntityID interface {
	String() string
	Valid() error
	Prefix() string
	Raw() string
	CheckString() string
	Regex() rext.Regex
}

const idlen = 24

const checklen = 1

const idCharset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
const idCharsetLen = len(idCharset)

var charSetReverseMap = generateCharsetMap()

const (
	prefixUserID         = "USR"
	prefixChannelID      = "CHA"
	prefixDeliveryID     = "DEL"
	prefixMessageID      = "MSG"
	prefixSubscriptionID = "SUB"
	prefixClientID       = "CLN"
	prefixRequestID      = "REQ"
	prefixKeyTokenID     = "TOK"
)

var (
	regexUserID         = generateRegex(prefixUserID)
	regexChannelID      = generateRegex(prefixChannelID)
	regexDeliveryID     = generateRegex(prefixDeliveryID)
	regexMessageID      = generateRegex(prefixMessageID)
	regexSubscriptionID = generateRegex(prefixSubscriptionID)
	regexClientID       = generateRegex(prefixClientID)
	regexRequestID      = generateRegex(prefixRequestID)
	regexKeyTokenID     = generateRegex(prefixKeyTokenID)
)

func generateRegex(prefix string) rext.Regex {
	return rext.W(regexp.MustCompile(fmt.Sprintf("^%s[%s]{%d}[%s]{%d}$", prefix, idCharset, idlen-len(prefix)-checklen, idCharset, checklen)))
}

func generateCharsetMap() []int {
	result := make([]int, 128)
	for i := 0; i < len(result); i++ {
		result[i] = -1
	}
	for idx, chr := range idCharset {
		result[int(chr)] = idx
	}
	return result
}

func generateID(prefix string) string {
	k := ""
	max := big.NewInt(int64(idCharsetLen))
	checksum := 0
	for i := 0; i < idlen-len(prefix)-checklen; i++ {
		v, err := rand.Int(rand.Reader, max)
		if err != nil {
			panic(err)
		}
		v64 := v.Int64()
		k += string(idCharset[v64])
		checksum = (checksum + int(v64)) % (idCharsetLen)
	}
	checkstr := string(idCharset[checksum%idCharsetLen])
	return prefix + k + checkstr
}

func validateID(prefix string, value string) error {
	if len(value) != idlen {
		return errors.New("id has the wrong length")
	}

	if !strings.HasPrefix(value, prefix) {
		return errors.New("id is missing the correct prefix")
	}

	checksum := 0
	for i := len(prefix); i < len(value)-checklen; i++ {
		ichr := int(value[i])
		if ichr < 0 || ichr >= len(charSetReverseMap) || charSetReverseMap[ichr] == -1 {
			return errors.New("id contains invalid characters")
		}
		checksum = (checksum + charSetReverseMap[ichr]) % (idCharsetLen)
	}

	checkstr := string(idCharset[checksum%idCharsetLen])

	if !strings.HasSuffix(value, checkstr) {
		return errors.New("id checkstring is invalid")
	}

	return nil
}

func getRawData(prefix string, value string) string {
	if len(value) != idlen {
		return ""
	}
	return value[len(prefix) : idlen-checklen]
}

func getCheckString(prefix string, value string) string {
	if len(value) != idlen {
		return ""
	}
	return value[idlen-checklen:]
}

func ValidateEntityID(vfl validator.FieldLevel) bool {
	if !vfl.Field().CanInterface() {
		log.Error().Msgf("Failed to validate EntityID (cannot interface ?!?)")
		return false
	}

	ifvalue := vfl.Field().Interface()

	if value1, ok := ifvalue.(EntityID); ok {

		if vfl.Field().Type().Kind() == reflect.Pointer && langext.IsNil(value1) {
			return true
		}

		if err := value1.Valid(); err != nil {
			log.Debug().Msgf("Failed to validate EntityID '%s' (%s)", value1.String(), err.Error())
			return false
		} else {
			return true
		}

	} else {
		log.Error().Msgf("Failed to validate EntityID (wrong type: %T)", ifvalue)
		return false
	}
}

// ------------------------------------------------------------

type UserID string

func NewUserID() UserID {
	return UserID(generateID(prefixUserID))
}

func (id UserID) Valid() error {
	return validateID(prefixUserID, string(id))
}

func (id UserID) String() string {
	return string(id)
}

func (id UserID) Prefix() string {
	return prefixUserID
}

func (id UserID) Raw() string {
	return getRawData(prefixUserID, string(id))
}

func (id UserID) CheckString() string {
	return getCheckString(prefixUserID, string(id))
}

func (id UserID) Regex() rext.Regex {
	return regexUserID
}

// ------------------------------------------------------------

type ChannelID string

func NewChannelID() ChannelID {
	return ChannelID(generateID(prefixChannelID))
}

func (id ChannelID) Valid() error {
	return validateID(prefixChannelID, string(id))
}

func (id ChannelID) String() string {
	return string(id)
}

func (id ChannelID) Prefix() string {
	return prefixChannelID
}

func (id ChannelID) Raw() string {
	return getRawData(prefixChannelID, string(id))
}

func (id ChannelID) CheckString() string {
	return getCheckString(prefixChannelID, string(id))
}

func (id ChannelID) Regex() rext.Regex {
	return regexChannelID
}

// ------------------------------------------------------------

type DeliveryID string

func NewDeliveryID() DeliveryID {
	return DeliveryID(generateID(prefixDeliveryID))
}

func (id DeliveryID) Valid() error {
	return validateID(prefixDeliveryID, string(id))
}

func (id DeliveryID) String() string {
	return string(id)
}

func (id DeliveryID) Prefix() string {
	return prefixDeliveryID
}

func (id DeliveryID) Raw() string {
	return getRawData(prefixDeliveryID, string(id))
}

func (id DeliveryID) CheckString() string {
	return getCheckString(prefixDeliveryID, string(id))
}

func (id DeliveryID) Regex() rext.Regex {
	return regexDeliveryID
}

// ------------------------------------------------------------

type MessageID string

func NewMessageID() MessageID {
	return MessageID(generateID(prefixMessageID))
}

func (id MessageID) Valid() error {
	return validateID(prefixMessageID, string(id))
}

func (id MessageID) String() string {
	return string(id)
}

func (id MessageID) Prefix() string {
	return prefixMessageID
}

func (id MessageID) Raw() string {
	return getRawData(prefixMessageID, string(id))
}

func (id MessageID) CheckString() string {
	return getCheckString(prefixMessageID, string(id))
}

func (id MessageID) Regex() rext.Regex {
	return regexMessageID
}

// ------------------------------------------------------------

type SubscriptionID string

func NewSubscriptionID() SubscriptionID {
	return SubscriptionID(generateID(prefixSubscriptionID))
}

func (id SubscriptionID) Valid() error {
	return validateID(prefixSubscriptionID, string(id))
}

func (id SubscriptionID) String() string {
	return string(id)
}

func (id SubscriptionID) Prefix() string {
	return prefixSubscriptionID
}

func (id SubscriptionID) Raw() string {
	return getRawData(prefixSubscriptionID, string(id))
}

func (id SubscriptionID) CheckString() string {
	return getCheckString(prefixSubscriptionID, string(id))
}

func (id SubscriptionID) Regex() rext.Regex {
	return regexSubscriptionID
}

// ------------------------------------------------------------

type ClientID string

func NewClientID() ClientID {
	return ClientID(generateID(prefixClientID))
}

func (id ClientID) Valid() error {
	return validateID(prefixClientID, string(id))
}

func (id ClientID) String() string {
	return string(id)
}

func (id ClientID) Prefix() string {
	return prefixClientID
}

func (id ClientID) Raw() string {
	return getRawData(prefixClientID, string(id))
}

func (id ClientID) CheckString() string {
	return getCheckString(prefixClientID, string(id))
}

func (id ClientID) Regex() rext.Regex {
	return regexClientID
}

// ------------------------------------------------------------

type RequestID string

func NewRequestID() RequestID {
	return RequestID(generateID(prefixRequestID))
}

func (id RequestID) Valid() error {
	return validateID(prefixRequestID, string(id))
}

func (id RequestID) String() string {
	return string(id)
}

func (id RequestID) Prefix() string {
	return prefixRequestID
}

func (id RequestID) Raw() string {
	return getRawData(prefixRequestID, string(id))
}

func (id RequestID) CheckString() string {
	return getCheckString(prefixRequestID, string(id))
}

func (id RequestID) Regex() rext.Regex {
	return regexRequestID
}

// ------------------------------------------------------------

type KeyTokenID string

func NewKeyTokenID() KeyTokenID {
	return KeyTokenID(generateID(prefixKeyTokenID))
}

func (id KeyTokenID) Valid() error {
	return validateID(prefixKeyTokenID, string(id))
}

func (id KeyTokenID) String() string {
	return string(id)
}

func (id KeyTokenID) Prefix() string {
	return prefixKeyTokenID
}

func (id KeyTokenID) Raw() string {
	return getRawData(prefixKeyTokenID, string(id))
}

func (id KeyTokenID) CheckString() string {
	return getCheckString(prefixKeyTokenID, string(id))
}

func (id KeyTokenID) Regex() rext.Regex {
	return regexKeyTokenID
}
