package auth

import (
	"crypto/rand"
	"encoding/base64"
	"time"

	"github.com/1Panel-dev/1Panel/core/utils/ttlstore"
)

const (
	MFASessionTTL             = 5 * time.Minute
	MFASessionStoreMaxEntries = 1024
	MFASessionMaxFailures     = 5
)

var mfaSessions = newMFASessionStore()

func GetMFASessionStore() *mfaSessionStore {
	return mfaSessions
}

type mfaSession struct {
	Name                     string
	Entrance                 string
	IP                       string
	AuthSource               string
	AuthSourceID             uint
	AuthSourceConfigVersion  uint64
	ExternalIssuer           string
	ExternalNameID           string
	ExternalNameIDFormat     string
	ExternalSessionIndex     string
	ExternalSessionExpiresAt time.Time
	ExternalSessionRequired  bool
	Failures                 int
	ExpiresAt                time.Time
}

type mfaSessionStore struct {
	store *ttlstore.Store[mfaSession]
}

func newMFASessionStore() *mfaSessionStore {
	return &mfaSessionStore{
		store: ttlstore.New[mfaSession](MFASessionTTL, MFASessionStoreMaxEntries, generateMFASessionID),
	}
}

func (s *mfaSessionStore) Set(name, entrance, ip string) string {
	return s.store.Set(mfaSession{
		Name:     name,
		Entrance: entrance,
		IP:       ip,
	})
}

func (s *mfaSessionStore) SetWithAuthSource(
	name, entrance, ip, authSource string,
	authSourceID uint,
	authSourceConfigVersion uint64,
) string {
	return s.store.Set(mfaSession{
		Name:                    name,
		Entrance:                entrance,
		IP:                      ip,
		AuthSource:              authSource,
		AuthSourceID:            authSourceID,
		AuthSourceConfigVersion: authSourceConfigVersion,
	})
}

func (s *mfaSessionStore) SetWithAuthSourceSession(
	name, entrance, ip, authSource string,
	authSourceID uint,
	authSourceConfigVersion uint64,
	externalIssuer, externalNameID, externalNameIDFormat, externalSessionIndex string,
	externalSessionExpiresAt time.Time,
	externalSessionRequired bool,
) string {
	return s.store.Set(mfaSession{
		Name: name, Entrance: entrance, IP: ip,
		AuthSource: authSource, AuthSourceID: authSourceID,
		AuthSourceConfigVersion: authSourceConfigVersion,
		ExternalIssuer:          externalIssuer, ExternalNameID: externalNameID,
		ExternalNameIDFormat:     externalNameIDFormat,
		ExternalSessionIndex:     externalSessionIndex,
		ExternalSessionExpiresAt: externalSessionExpiresAt,
		ExternalSessionRequired:  externalSessionRequired,
	})
}

func (s *mfaSessionStore) Get(sessionID string) (mfaSession, bool) {
	return s.store.Get(sessionID)
}

func (s *mfaSessionStore) Delete(sessionID string) {
	s.store.Delete(sessionID)
}

func (s *mfaSessionStore) RecordFailure(sessionID string) int {
	failures := 0
	ok := s.store.Update(sessionID, func(item *mfaSession) bool {
		item.Failures++
		failures = item.Failures
		return item.Failures < MFASessionMaxFailures
	})
	if !ok {
		return 0
	}
	return failures
}

func generateMFASessionID() string {
	raw := make([]byte, 32)
	if _, err := rand.Read(raw); err != nil {
		return generateFallbackMFASessionID()
	}
	return base64.RawURLEncoding.EncodeToString(raw)
}

func generateFallbackMFASessionID() string {
	return base64.RawURLEncoding.EncodeToString([]byte(time.Now().Format(time.RFC3339Nano)))
}
