package main

import (
	"context"
	"log"
	"net/http"
	"sync"
	"time"

	"sogoms.com/internal/admin"
)

// contextKey est une clé pour le contexte.
type contextKey string

const (
	ctxSession contextKey = "session"
	ctxUser    contextKey = "user"
)

// GetSessionFromContext récupère la session depuis le contexte.
func GetSessionFromContext(ctx context.Context) *Session {
	if session, ok := ctx.Value(ctxSession).(*Session); ok {
		return session
	}
	return nil
}

// GetUserFromContext récupère l'utilisateur depuis le contexte.
func GetUserFromContext(ctx context.Context) *admin.AdminUser {
	if user, ok := ctx.Value(ctxUser).(*admin.AdminUser); ok {
		return user
	}
	return nil
}

// AuthMiddleware vérifie que l'utilisateur est authentifié.
func AuthMiddleware(sessions *SessionStore, adminCfg *admin.AdminConfig, next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		session, err := sessions.GetSessionFromRequest(r)
		if err != nil {
			log.Printf("[admin] auth failed: %v", err)
			http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
			return
		}

		// Récupérer l'utilisateur
		user := adminCfg.GetUser(session.Username)
		if user == nil {
			log.Printf("[admin] user not found: %s", session.Username)
			sessions.Delete(session.ID)
			sessions.ClearCookie(w)
			http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
			return
		}

		// Vérifier si la session est en attente de 2FA
		if session.TwoFAPending {
			http.Redirect(w, r, "/admin/2fa/verify", http.StatusSeeOther)
			return
		}

		// Prolonger la session (sliding expiration)
		sessions.Refresh(session.ID)

		// Injecter dans le contexte
		ctx := context.WithValue(r.Context(), ctxSession, session)
		ctx = context.WithValue(ctx, ctxUser, user)

		next(w, r.WithContext(ctx))
	}
}

// TwoFASetupMiddleware permet l'accès à la page de setup 2FA même sans 2FA vérifié.
// Utilisé uniquement pour les routes de configuration 2FA.
func TwoFASetupMiddleware(sessions *SessionStore, adminCfg *admin.AdminConfig, next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		session, err := sessions.GetSessionFromRequest(r)
		if err != nil {
			http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
			return
		}

		// Récupérer l'utilisateur
		user := adminCfg.GetUser(session.Username)
		if user == nil {
			sessions.Delete(session.ID)
			sessions.ClearCookie(w)
			http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
			return
		}

		// Prolonger la session
		sessions.Refresh(session.ID)

		// Injecter dans le contexte
		ctx := context.WithValue(r.Context(), ctxSession, session)
		ctx = context.WithValue(ctx, ctxUser, user)

		next(w, r.WithContext(ctx))
	}
}

// CSRFMiddleware vérifie le token CSRF pour les requêtes POST/PUT/DELETE.
func CSRFMiddleware(sessions *SessionStore, next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// Seules les requêtes de modification nécessitent CSRF
		if r.Method == "GET" || r.Method == "HEAD" || r.Method == "OPTIONS" {
			next(w, r)
			return
		}

		session, err := sessions.GetSessionFromRequest(r)
		if err != nil {
			http.Error(w, "Invalid session", http.StatusForbidden)
			return
		}

		// Récupérer le token CSRF (header ou form)
		csrfToken := r.Header.Get("X-CSRF-Token")
		if csrfToken == "" {
			csrfToken = r.FormValue("csrf_token")
		}

		if csrfToken == "" || csrfToken != session.CSRFToken {
			log.Printf("[admin] CSRF validation failed for %s", session.Username)
			http.Error(w, "Invalid CSRF token", http.StatusForbidden)
			return
		}

		next(w, r)
	}
}

// RateLimiter limite les tentatives de login.
type RateLimiter struct {
	attempts map[string][]time.Time // IP -> timestamps
	config   *admin.RateLimitConfig
	mu       sync.Mutex
}

// NewRateLimiter crée un nouveau rate limiter.
func NewRateLimiter(config *admin.RateLimitConfig) *RateLimiter {
	return &RateLimiter{
		attempts: make(map[string][]time.Time),
		config:   config,
	}
}

// Allow vérifie si une IP peut tenter un login.
func (r *RateLimiter) Allow(ip string) bool {
	r.mu.Lock()
	defer r.mu.Unlock()

	now := time.Now()
	window := time.Duration(r.config.LoginWindow) * time.Second
	cutoff := now.Add(-window)

	// Nettoyer les anciennes tentatives
	var recent []time.Time
	for _, t := range r.attempts[ip] {
		if t.After(cutoff) {
			recent = append(recent, t)
		}
	}
	r.attempts[ip] = recent

	return len(recent) < r.config.LoginMax
}

// Record enregistre une tentative.
func (r *RateLimiter) Record(ip string) {
	r.mu.Lock()
	defer r.mu.Unlock()

	r.attempts[ip] = append(r.attempts[ip], time.Now())
}

// Remaining retourne le nombre de tentatives restantes.
func (r *RateLimiter) Remaining(ip string) int {
	r.mu.Lock()
	defer r.mu.Unlock()

	now := time.Now()
	window := time.Duration(r.config.LoginWindow) * time.Second
	cutoff := now.Add(-window)

	var count int
	for _, t := range r.attempts[ip] {
		if t.After(cutoff) {
			count++
		}
	}

	remaining := r.config.LoginMax - count
	if remaining < 0 {
		remaining = 0
	}
	return remaining
}

// RateLimitMiddleware applique le rate limiting.
func RateLimitMiddleware(rateLimiter *RateLimiter, next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ip := getClientIP(r)

		if !rateLimiter.Allow(ip) {
			log.Printf("[admin] rate limit exceeded for IP: %s", ip)
			http.Error(w, "Too many attempts. Please try again later.", http.StatusTooManyRequests)
			return
		}

		next(w, r)
	}
}

// LoggingMiddleware log toutes les requêtes.
func LoggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()

		// Wrapper pour capturer le status code
		lw := &loggingResponseWriter{ResponseWriter: w, statusCode: http.StatusOK}

		next.ServeHTTP(lw, r)

		log.Printf("[admin] %s %s %d %s", r.Method, r.URL.Path, lw.statusCode, time.Since(start))
	})
}

// loggingResponseWriter capture le status code.
type loggingResponseWriter struct {
	http.ResponseWriter
	statusCode int
}

func (lw *loggingResponseWriter) WriteHeader(code int) {
	lw.statusCode = code
	lw.ResponseWriter.WriteHeader(code)
}

// getClientIP récupère l'IP du client.
func getClientIP(r *http.Request) string {
	// Vérifier X-Forwarded-For (proxy/load balancer)
	if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
		// Prendre la première IP
		if idx := len(xff); idx > 0 {
			for i, c := range xff {
				if c == ',' {
					return xff[:i]
				}
			}
			return xff
		}
	}

	// Vérifier X-Real-IP
	if xri := r.Header.Get("X-Real-IP"); xri != "" {
		return xri
	}

	// Fallback sur RemoteAddr
	ip := r.RemoteAddr
	// Enlever le port
	for i := len(ip) - 1; i >= 0; i-- {
		if ip[i] == ':' {
			return ip[:i]
		}
	}
	return ip
}
