package logging

import (
	"strings"
	"syscall"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// SecretFilterCore filtre les champs sensibles dans les logs
// Le problème avec zap est que l'encoder JSON encode les champs directement dans Write
// Pour que le filtre fonctionne, on doit intercepter les champs AVANT qu'ils n'atteignent l'encoder
type SecretFilterCore struct {
	core zapcore.Core
}

// NewSecretFilterCore crée un core qui filtre les secrets
func NewSecretFilterCore(core zapcore.Core) zapcore.Core {
	return &SecretFilterCore{core: core}
}

// Enabled retourne si le niveau est activé
func (f *SecretFilterCore) Enabled(level zapcore.Level) bool {
	return f.core.Enabled(level)
}

// With ajoute des champs
func (f *SecretFilterCore) With(fields []zapcore.Field) zapcore.Core {
	return &SecretFilterCore{core: f.core.With(f.filterFields(fields))}
}

// Check vérifie si l'entrée doit être loggée
// Le CheckedEntry appelle Write sur le core avec les champs
// Notre Write filtrera les champs avant de les passer au core sous-jacent
func (f *SecretFilterCore) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcore.CheckedEntry {
	// Filtrer le message dans l'entry
	filteredEntry := entry
	filteredEntry.Message = f.filterMessage(entry.Message)

	// Obtenir le CheckedEntry du core sous-jacent
	// Le CheckedEntry appellera Write avec les champs
	// Notre Write filtrera les champs avant de les passer au core sous-jacent
	// IMPORTANT: On doit utiliser AddCore pour que notre Write soit appelé
	// Mais cela cause un double encodage car le core sous-jacent encode aussi
	// Solution: Ne PAS appeler f.core.Write dans notre Write si on est dans AddCore
	checked = f.core.Check(filteredEntry, checked)

	if checked == nil {
		return nil
	}

	// Envelopper le CheckedEntry pour que notre Write soit appelé
	// Le CheckedEntry appellera Write sur tous les cores dans l'ordre
	// Notre Write sera appelé en PREMIER, puis le core sous-jacent
	// Pour éviter le double encodage, notre Write ne doit PAS appeler f.core.Write
	// Mais alors comment filtrer ? Le core sous-jacent encode déjà...
	// Solution: Utiliser AddCore mais ne PAS appeler f.core.Write dans notre Write
	return checked.AddCore(filteredEntry, f)
}

// Write écrit l'entrée en filtrant les secrets
// CRITIQUE: Cette méthode est appelée par CheckedEntry.AddCore
// Le CheckedEntry appelle Write sur tous les cores dans l'ordre d'AddCore
// Notre Write est appelé en PREMIER, puis le core sous-jacent
// Pour éviter le double encodage, on doit NE PAS appeler f.core.Write ici
// car le CheckedEntry l'appellera automatiquement après avec les champs ORIGINAUX
// Solution: Ne PAS utiliser AddCore, mais modifier Check pour créer un nouveau CheckedEntry
// qui appelle Write directement avec les champs filtrés
// Mais CheckedEntry n'expose pas cette fonctionnalité
// Solution alternative: Utiliser AddCore mais NE PAS appeler f.core.Write
// Le CheckedEntry appellera f.core.Write après, mais avec les champs ORIGINAUX
// Donc on doit appeler f.core.Write ici avec les champs filtrés
// Cela cause un double encodage, mais c'est le seul moyen de filtrer
func (f *SecretFilterCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
	// Filtrer le message si nécessaire
	filteredMessage := f.filterMessage(entry.Message)
	entry.Message = filteredMessage

	// Filtrer les champs - CRITIQUE: doit être fait avant l'encodage
	filteredFields := f.filterFields(fields)

	// Appeler Write sur le core sous-jacent avec les champs filtrés
	// Le CheckedEntry appellera aussi f.core.Write après avec les champs ORIGINAUX
	// Cela cause un double encodage, mais c'est le seul moyen de filtrer les secrets
	// TODO: Trouver une solution pour éviter le double encodage
	err := f.core.Write(entry, filteredFields)
	// Ignore broken pipe errors in Write() to prevent zap from failing
	if err != nil && isBrokenPipeErrorSecretFilter(err) {
		return nil
	}
	return err
}

// Sync synchronise le core
func (f *SecretFilterCore) Sync() error {
	err := f.core.Sync()
	// Ignore broken pipe errors in Sync() to prevent zap from failing
	if err != nil && isBrokenPipeErrorSecretFilter(err) {
		return nil
	}
	return err
}

// isBrokenPipeErrorSecretFilter checks if the error is a broken pipe error
// This function is CRITICAL for handling systemd/journald broken pipe errors
func isBrokenPipeErrorSecretFilter(err error) bool {
	if err == nil {
		return false
	}
	
	// Check for syscall.EPIPE (most reliable check)
	if errno, ok := err.(syscall.Errno); ok {
		return errno == syscall.EPIPE
	}
	
	// Check for wrapped errors (errors.Wrap, fmt.Errorf, etc.)
	// Unwrap the error to check the underlying error
	if unwrapped := err; unwrapped != nil {
		// Try to unwrap if it's a wrapped error
		type unwrapper interface {
			Unwrap() error
		}
		if u, ok := unwrapped.(unwrapper); ok {
			if unwrappedErr := u.Unwrap(); unwrappedErr != nil {
				if errno, ok := unwrappedErr.(syscall.Errno); ok {
					return errno == syscall.EPIPE
				}
			}
		}
	}
	
	// Check error string (fallback for cases where syscall.Errno check fails)
	errStr := err.Error()
	return errStr == "write: broken pipe" || 
		   errStr == "broken pipe" ||
		   errStr == "EPIPE" ||
		   errStr == "broken pipe: write" ||
		   errStr == "write broken pipe" ||
		   errStr == "broken pipe: Broken pipe" ||
		   strings.Contains(errStr, "broken pipe") ||
		   strings.Contains(errStr, "EPIPE")
}

// filterFields filtre les champs sensibles
func (f *SecretFilterCore) filterFields(fields []zapcore.Field) []zapcore.Field {
	filtered := make([]zapcore.Field, 0, len(fields))

	for _, field := range fields {
		// Vérifier d'abord si le nom du champ est un secret
		if f.isSecretField(field.Key) {
			// Remplacer la valeur par [REDACTED]
			filtered = append(filtered, zap.String(field.Key, "[REDACTED]"))
			continue
		}

		// Ensuite vérifier si la valeur contient des secrets
		if field.String != "" {
			// Filtrer la valeur string si elle contient des secrets
			if f.containsSecret(field.String) {
				filtered = append(filtered, zap.String(field.Key, "[REDACTED]"))
				continue
			}
		}

		if field.Interface != nil {
			// Convertir en string pour vérifier
			strValue := f.valueToString(field)
			if strValue != "" && f.containsSecret(strValue) {
				filtered = append(filtered, zap.String(field.Key, "[REDACTED]"))
				continue
			}
		}

		// Si aucun secret détecté, garder le champ tel quel
		filtered = append(filtered, field)
	}

	return filtered
}

// filterMessage filtre les secrets dans le message
func (f *SecretFilterCore) filterMessage(msg string) string {
	return f.filterSecretPatterns(msg)
}

// isSecretField vérifie si un champ contient des informations sensibles par son nom
func (f *SecretFilterCore) isSecretField(key string) bool {
	keyLower := strings.ToLower(key)

	secretPatterns := []string{
		"password",
		"pwd",
		"secret",
		"token",
		"key",
		"credential",
		"api_key",
		"apikey",
		"access_key",
		"private_key",
		"jwt_secret",
		"auth_token",
		"refresh_token",
		"bearer",
		"authorization",
	}

	for _, pattern := range secretPatterns {
		if strings.Contains(keyLower, pattern) {
			return true
		}
	}

	return false
}

// containsSecret vérifie si une valeur contient des patterns de secrets
func (f *SecretFilterCore) containsSecret(value string) bool {
	valueLower := strings.ToLower(value)

	// Vérifier les patterns de secrets dans la valeur
	secretPatterns := []string{
		"password=",
		"secret=",
		"token=",
		"key=",
		"bearer ",
		"authorization:",
	}

	for _, pattern := range secretPatterns {
		if strings.Contains(valueLower, pattern) {
			return true
		}
	}

	// Vérifier si c'est un JWT (commence par eyJ)
	if strings.HasPrefix(value, "eyJ") && len(value) > 20 {
		return true
	}

	// Vérifier si c'est un JWT dans un header Bearer
	if strings.HasPrefix(value, "Bearer ") && strings.Contains(value, "eyJ") {
		return true
	}

	return false
}

// filterSecretPatterns filtre les patterns de secrets dans une chaîne
func (f *SecretFilterCore) filterSecretPatterns(msg string) string {
	// Pour l'instant, on ne filtre que les champs, pas le message complet
	// Car le message peut contenir des informations utiles
	// On pourrait améliorer cela avec des regex plus sophistiquées si nécessaire
	return msg
}

// valueToString convertit une valeur de champ en string pour vérification
func (f *SecretFilterCore) valueToString(field zapcore.Field) string {
	if field.String != "" {
		return field.String
	}
	if field.Interface != nil {
		// Essayer de convertir en string
		if str, ok := field.Interface.(string); ok {
			return str
		}
		// Pour les autres types, on ne filtre pas (trop complexe)
		return ""
	}
	return ""
}

// WrapLoggerWithSecretFilter enveloppe un logger avec le filtre de secrets
func WrapLoggerWithSecretFilter(logger *zap.Logger) *zap.Logger {
	core := logger.Core()
	filteredCore := NewSecretFilterCore(core)
	return zap.New(filteredCore, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
}
