package main

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"net/http"
	"strings"
	"sync"
	"time"

	"sogoms.com/internal/admin"
)

// Session représente une session utilisateur.
type Session struct {
	ID        string
	Username  string
	Role      string
	CSRFToken string
	CreatedAt time.Time
	ExpiresAt time.Time
	IP        string
	UserAgent string
	// 2FA
	TwoFAPending  bool // true si en attente de validation 2FA
	TwoFAVerified bool // true après validation 2FA réussie
}

// IsExpired vérifie si la session a expiré.
func (s *Session) IsExpired() bool {
	return time.Now().After(s.ExpiresAt)
}

// SessionStore gère les sessions en mémoire.
type SessionStore struct {
	sessions map[string]*Session
	config   *admin.SessionConfig
	mu       sync.RWMutex
}

// NewSessionStore crée un nouveau store de sessions.
func NewSessionStore(config *admin.SessionConfig) *SessionStore {
	return &SessionStore{
		sessions: make(map[string]*Session),
		config:   config,
	}
}

// Create crée une nouvelle session.
func (s *SessionStore) Create(username, role, ip, userAgent string) (*Session, error) {
	sessionID, err := generateSecureToken(32)
	if err != nil {
		return nil, err
	}

	csrfToken, err := generateSecureToken(32)
	if err != nil {
		return nil, err
	}

	now := time.Now()
	session := &Session{
		ID:        sessionID,
		Username:  username,
		Role:      role,
		CSRFToken: csrfToken,
		CreatedAt: now,
		ExpiresAt: now.Add(time.Duration(s.config.MaxAge) * time.Second),
		IP:        ip,
		UserAgent: userAgent,
	}

	s.mu.Lock()
	s.sessions[sessionID] = session
	s.mu.Unlock()

	return session, nil
}

// CreatePending crée une session en attente de validation 2FA (expire en 5 min).
func (s *SessionStore) CreatePending(username, role, ip, userAgent string) (*Session, error) {
	sessionID, err := generateSecureToken(32)
	if err != nil {
		return nil, err
	}

	csrfToken, err := generateSecureToken(32)
	if err != nil {
		return nil, err
	}

	now := time.Now()
	session := &Session{
		ID:            sessionID,
		Username:      username,
		Role:          role,
		CSRFToken:     csrfToken,
		CreatedAt:     now,
		ExpiresAt:     now.Add(5 * time.Minute), // expiration courte pour 2FA
		IP:            ip,
		UserAgent:     userAgent,
		TwoFAPending:  true,
		TwoFAVerified: false,
	}

	s.mu.Lock()
	s.sessions[sessionID] = session
	s.mu.Unlock()

	return session, nil
}

// CompleteTwoFA marque la session comme ayant passé la 2FA.
func (s *SessionStore) CompleteTwoFA(sessionID string) {
	s.mu.Lock()
	if session, ok := s.sessions[sessionID]; ok {
		session.TwoFAPending = false
		session.TwoFAVerified = true
		// Prolonger l'expiration après validation 2FA
		session.ExpiresAt = time.Now().Add(time.Duration(s.config.MaxAge) * time.Second)
	}
	s.mu.Unlock()
}

// Get récupère une session par son ID.
func (s *SessionStore) Get(sessionID string) (*Session, bool) {
	s.mu.RLock()
	session, ok := s.sessions[sessionID]
	s.mu.RUnlock()

	if !ok || session.IsExpired() {
		return nil, false
	}

	return session, true
}

// Delete supprime une session.
func (s *SessionStore) Delete(sessionID string) {
	s.mu.Lock()
	delete(s.sessions, sessionID)
	s.mu.Unlock()
}

// Refresh prolonge la durée de vie d'une session (sliding expiration).
func (s *SessionStore) Refresh(sessionID string) {
	s.mu.Lock()
	if session, ok := s.sessions[sessionID]; ok {
		session.ExpiresAt = time.Now().Add(time.Duration(s.config.MaxAge) * time.Second)
	}
	s.mu.Unlock()
}

// Cleanup supprime les sessions expirées.
func (s *SessionStore) Cleanup() {
	ticker := time.NewTicker(5 * time.Minute)
	defer ticker.Stop()

	for range ticker.C {
		s.mu.Lock()
		now := time.Now()
		for id, session := range s.sessions {
			if now.After(session.ExpiresAt) {
				delete(s.sessions, id)
			}
		}
		s.mu.Unlock()
	}
}

// Count retourne le nombre de sessions actives.
func (s *SessionStore) Count() int {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return len(s.sessions)
}

// SetCookie définit le cookie de session dans la réponse.
func (s *SessionStore) SetCookie(w http.ResponseWriter, session *Session) {
	// Signer le session ID
	signature := s.sign(session.ID)
	value := session.ID + "." + signature

	http.SetCookie(w, &http.Cookie{
		Name:     s.config.CookieName,
		Value:    value,
		Path:     "/admin",
		MaxAge:   s.config.MaxAge,
		HttpOnly: true,
		Secure:   true,
		SameSite: http.SameSiteStrictMode,
	})
}

// ClearCookie supprime le cookie de session.
func (s *SessionStore) ClearCookie(w http.ResponseWriter) {
	http.SetCookie(w, &http.Cookie{
		Name:     s.config.CookieName,
		Value:    "",
		Path:     "/admin",
		MaxAge:   -1,
		HttpOnly: true,
		Secure:   true,
		SameSite: http.SameSiteStrictMode,
	})
}

// GetSessionFromRequest extrait et valide la session depuis le cookie.
func (s *SessionStore) GetSessionFromRequest(r *http.Request) (*Session, error) {
	cookie, err := r.Cookie(s.config.CookieName)
	if err != nil {
		return nil, fmt.Errorf("no session cookie")
	}

	// Séparer ID et signature
	parts := strings.SplitN(cookie.Value, ".", 2)
	if len(parts) != 2 {
		return nil, fmt.Errorf("invalid cookie format")
	}

	sessionID := parts[0]
	signature := parts[1]

	// Vérifier la signature
	if !s.verify(sessionID, signature) {
		return nil, fmt.Errorf("invalid cookie signature")
	}

	// Récupérer la session
	session, ok := s.Get(sessionID)
	if !ok {
		return nil, fmt.Errorf("session not found or expired")
	}

	return session, nil
}

// sign signe une donnée avec HMAC-SHA256.
func (s *SessionStore) sign(data string) string {
	h := hmac.New(sha256.New, []byte(s.config.Secret))
	h.Write([]byte(data))
	return base64.RawURLEncoding.EncodeToString(h.Sum(nil))
}

// verify vérifie une signature HMAC.
func (s *SessionStore) verify(data, signature string) bool {
	expected := s.sign(data)
	return hmac.Equal([]byte(expected), []byte(signature))
}

// generateSecureToken génère un token aléatoire sécurisé.
func generateSecureToken(length int) (string, error) {
	bytes := make([]byte, length)
	if _, err := rand.Read(bytes); err != nil {
		return "", err
	}
	return hex.EncodeToString(bytes), nil
}
