package totpext

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha1"
	"encoding/base32"
	"encoding/binary"
	"fmt"
	"hash"
	"net/url"
	"strconv"
	"time"
)

// https://datatracker.ietf.org/doc/html/rfc6238
// https://datatracker.ietf.org/doc/html/rfc4226
// https://datatracker.ietf.org/doc/html/rfc2104

// https://en.wikipedia.org/wiki/Universal_2nd_Factor
// https://en.wikipedia.org/wiki/HMAC-based_one-time_password
// https://en.wikipedia.org/wiki/HMAC

func TOTP(key []byte) string {
	t := time.Now().Unix() / 30
	return generateTOTP(sha1.New, key, t, 6)
}

func Validate(key []byte, totp string) bool {
	t := time.Now().Unix() / 30

	if generateTOTP(sha1.New, key, t, 6) == totp {
		return true
	}

	if generateTOTP(sha1.New, key, t-1, 6) == totp {
		return true
	}

	if generateTOTP(sha1.New, key, t+1, 6) == totp {
		return true
	}

	return false
}

func GenerateSecret() ([]byte, error) {
	secret := make([]byte, 20)
	_, err := rand.Read(secret)
	if err != nil {
		return nil, err
	}
	return secret, nil
}

func generateTOTP(algo func() hash.Hash, secret []byte, time int64, returnDigits int) string {
	msg := make([]byte, 8)
	binary.BigEndian.PutUint64(msg, uint64(time))

	mac := hmac.New(algo, secret)
	mac.Write(msg)
	hmacResult := mac.Sum(nil)

	offsetBits := hmacResult[len(hmacResult)-1] & 0x0F

	p := hmacResult[offsetBits : offsetBits+4]

	truncated := binary.BigEndian.Uint32(p) & 0x7FFFFFFF // Last 31 bits

	val := strconv.Itoa(int(truncated))
	for len(val) < returnDigits {
		val = "0" + val
	}
	val = val[len(val)-returnDigits:]

	return val
}

func GenerateOTPAuth(ccn string, key []byte, accountmail string, issuer string) string {

	return fmt.Sprintf("otpauth://totp/%v:%v?secret=%v&issuer=%v&algorithm=%v&period=%v&digits=%v",
		ccn,
		url.QueryEscape(accountmail),
		base32.StdEncoding.EncodeToString(key),
		issuer,
		"SHA1",
		"30",
		"6")
}
