// Package auth provides TOTP and token authentication for INP2P.
package auth

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/binary"
	"fmt"
	"hash/crc64"
	"time"
)

const (
	// TOTPStep is the time window in seconds for TOTP validity.
	// A code is valid for ±1 step to allow for clock drift.
	TOTPStep int64 = 60
)

var crcTable = crc64.MakeTable(crc64.ECMA)

// MakeToken generates a token from user+password using CRC64.
func MakeToken(user, password string) uint64 {
	return crc64.Checksum([]byte(user+password), crcTable)
}

// GenTOTP generates a TOTP code for relay authentication.
func GenTOTP(token uint64, ts int64) uint64 {
	step := ts / TOTPStep
	buf := make([]byte, 16)
	binary.BigEndian.PutUint64(buf[:8], token)
	binary.BigEndian.PutUint64(buf[8:], uint64(step))

	mac := hmac.New(sha256.New, buf[:8])
	mac.Write(buf[8:])
	sum := mac.Sum(nil)

	return binary.BigEndian.Uint64(sum[:8])
}

// VerifyTOTP verifies a TOTP code with ±1 step tolerance.
func VerifyTOTP(code uint64, token uint64, ts int64) bool {
	for delta := int64(-1); delta <= 1; delta++ {
		expected := GenTOTP(token, ts+delta*TOTPStep)
		if code == expected {
			return true
		}
	}
	return false
}

// RelayToken generates a one-time relay token signed by the server.
// Used for cross-user super relay authentication.
type RelayToken struct {
	SessionID string `json:"sessionID"`
	From      string `json:"from"`
	To        string `json:"to"`
	Relay     string `json:"relay"`
	Expires   int64  `json:"expires"`
	Signature []byte `json:"signature"`
}

// SignRelayToken creates a signed one-time relay token.
func SignRelayToken(secret []byte, sessionID, from, to, relay string, ttl time.Duration) RelayToken {
	rt := RelayToken{
		SessionID: sessionID,
		From:      from,
		To:        to,
		Relay:     relay,
		Expires:   time.Now().Add(ttl).Unix(),
	}

	msg := fmt.Sprintf("%s:%s:%s:%s:%d", rt.SessionID, rt.From, rt.To, rt.Relay, rt.Expires)
	mac := hmac.New(sha256.New, secret)
	mac.Write([]byte(msg))
	rt.Signature = mac.Sum(nil)

	return rt
}

// VerifyRelayToken validates a signed relay token.
func VerifyRelayToken(secret []byte, rt RelayToken) bool {
	if time.Now().Unix() > rt.Expires {
		return false
	}

	msg := fmt.Sprintf("%s:%s:%s:%s:%d", rt.SessionID, rt.From, rt.To, rt.Relay, rt.Expires)
	mac := hmac.New(sha256.New, secret)
	mac.Write([]byte(msg))
	expected := mac.Sum(nil)

	return hmac.Equal(rt.Signature, expected)
}
