package security

import (
	"crypto/rand"
	"encoding/base32"
	"fmt"
	"time"

	"github.com/pquerna/otp/totp"
)

// MFAMethod représente une méthode MFA
type MFAMethod struct {
	ID         string    `json:"id"`
	UserID     string    `json:"user_id"`
	Type       string    `json:"type"` // totp, sms, email, backup
	Secret     string    `json:"secret,omitempty"`
	Phone      string    `json:"phone,omitempty"`
	Email      string    `json:"email,omitempty"`
	IsActive   bool      `json:"is_active"`
	IsVerified bool      `json:"is_verified"`
	CreatedAt  time.Time `json:"created_at"`
	VerifiedAt time.Time `json:"verified_at,omitempty"`
	LastUsedAt time.Time `json:"last_used_at,omitempty"`
}

// MFASession représente une session MFA
type MFASession struct {
	ID        string    `json:"id"`
	UserID    string    `json:"user_id"`
	MethodID  string    `json:"method_id"`
	Token     string    `json:"token"`
	ExpiresAt time.Time `json:"expires_at"`
	Used      bool      `json:"used"`
}

// MFAManager gère l'authentification multi-facteurs
type MFAManager struct {
	methods  map[string]*MFAMethod
	sessions map[string]*MFASession
}

// NewMFAManager crée un nouveau gestionnaire MFA
func NewMFAManager() *MFAManager {
	return &MFAManager{
		methods:  make(map[string]*MFAMethod),
		sessions: make(map[string]*MFASession),
	}
}

// GenerateTOTPSecret génère un secret TOTP
func (mfa *MFAManager) GenerateTOTPSecret(userID, email string) (*MFAMethod, error) {
	// Générer un secret aléatoire
	secret := make([]byte, 20)
	if _, err := rand.Read(secret); err != nil {
		return nil, fmt.Errorf("failed to generate secret: %w", err)
	}

	// Encoder en base32
	secretBase32 := base32.StdEncoding.EncodeToString(secret)

	// Créer la méthode TOTP
	method := &MFAMethod{
		ID:         fmt.Sprintf("totp_%s", userID),
		UserID:     userID,
		Type:       "totp",
		Secret:     secretBase32,
		IsActive:   false,
		IsVerified: false,
		CreatedAt:  time.Now(),
	}

	mfa.methods[method.ID] = method
	return method, nil
}

// GenerateTOTPQRCode génère le QR code pour TOTP
func (mfa *MFAManager) GenerateTOTPQRCode(method *MFAMethod, issuer, accountName string) string {
	// Format: otpauth://totp/issuer:account?secret=secret&issuer=issuer
	url := fmt.Sprintf("otpauth://totp/%s:%s?secret=%s&issuer=%s",
		issuer, accountName, method.Secret, issuer)
	return url
}

// VerifyTOTP vérifie un code TOTP
func (mfa *MFAManager) VerifyTOTP(methodID, code string) (bool, error) {
	method, exists := mfa.methods[methodID]
	if !exists {
		return false, fmt.Errorf("method not found")
	}

	if method.Type != "totp" {
		return false, fmt.Errorf("method is not TOTP")
	}

	// Vérifier le code TOTP
	valid := totp.Validate(code, method.Secret)
	if valid {
		method.LastUsedAt = time.Now()
		if !method.IsVerified {
			method.IsVerified = true
			method.VerifiedAt = time.Now()
		}
	}

	return valid, nil
}

// GenerateBackupCodes génère des codes de sauvegarde
func (mfa *MFAManager) GenerateBackupCodes(userID string, count int) ([]string, error) {
	codes := make([]string, count)

	for i := 0; i < count; i++ {
		// Générer un code de 8 caractères
		codeBytes := make([]byte, 4)
		if _, err := rand.Read(codeBytes); err != nil {
			return nil, fmt.Errorf("failed to generate backup code: %w", err)
		}

		// Encoder en base32 et prendre les 8 premiers caractères
		code := base32.StdEncoding.EncodeToString(codeBytes)[:8]
		codes[i] = code
	}

	// Créer la méthode de sauvegarde
	method := &MFAMethod{
		ID:         fmt.Sprintf("backup_%s", userID),
		UserID:     userID,
		Type:       "backup",
		Secret:     "", // Les codes sont stockés séparément
		IsActive:   true,
		IsVerified: true,
		CreatedAt:  time.Now(),
		VerifiedAt: time.Now(),
	}

	mfa.methods[method.ID] = method
	return codes, nil
}

// VerifyBackupCode vérifie un code de sauvegarde
func (mfa *MFAManager) VerifyBackupCode(userID, code string) (bool, error) {
	methodID := fmt.Sprintf("backup_%s", userID)
	method, exists := mfa.methods[methodID]
	if !exists {
		return false, fmt.Errorf("backup method not found")
	}

	// Dans un vrai système, les codes seraient stockés de manière sécurisée
	// Ici on simule la vérification
	valid := len(code) == 8 && method.IsActive
	if valid {
		method.LastUsedAt = time.Now()
	}

	return valid, nil
}

// GenerateSMSMFA génère une méthode MFA par SMS
func (mfa *MFAManager) GenerateSMSMFA(userID, phone string) (*MFAMethod, error) {
	method := &MFAMethod{
		ID:         fmt.Sprintf("sms_%s", userID),
		UserID:     userID,
		Type:       "sms",
		Phone:      phone,
		IsActive:   false,
		IsVerified: false,
		CreatedAt:  time.Now(),
	}

	mfa.methods[method.ID] = method
	return method, nil
}

// SendSMSCode envoie un code SMS
func (mfa *MFAManager) SendSMSCode(methodID string) (string, error) {
	method, exists := mfa.methods[methodID]
	if !exists {
		return "", fmt.Errorf("method not found")
	}

	if method.Type != "sms" {
		return "", fmt.Errorf("method is not SMS")
	}

	// Générer un code à 6 chiffres
	code := fmt.Sprintf("%06d", time.Now().UnixNano()%1000000)

	// Dans un vrai système, on enverrait le SMS via un service
	// Ici on simule l'envoi
	fmt.Printf("SMS code sent to %s: %s\n", method.Phone, code)

	return code, nil
}

// VerifySMSCode vérifie un code SMS
func (mfa *MFAManager) VerifySMSCode(methodID, code string) (bool, error) {
	method, exists := mfa.methods[methodID]
	if !exists {
		return false, fmt.Errorf("method not found")
	}

	if method.Type != "sms" {
		return false, fmt.Errorf("method is not SMS")
	}

	// Dans un vrai système, on vérifierait le code stocké
	// Ici on simule la vérification
	valid := len(code) == 6
	if valid {
		method.IsVerified = true
		method.VerifiedAt = time.Now()
		method.LastUsedAt = time.Now()
	}

	return valid, nil
}

// GenerateEmailMFA génère une méthode MFA par email
func (mfa *MFAManager) GenerateEmailMFA(userID, email string) (*MFAMethod, error) {
	method := &MFAMethod{
		ID:         fmt.Sprintf("email_%s", userID),
		UserID:     userID,
		Type:       "email",
		Email:      email,
		IsActive:   false,
		IsVerified: false,
		CreatedAt:  time.Now(),
	}

	mfa.methods[method.ID] = method
	return method, nil
}

// SendEmailCode envoie un code par email
func (mfa *MFAManager) SendEmailCode(methodID string) (string, error) {
	method, exists := mfa.methods[methodID]
	if !exists {
		return "", fmt.Errorf("method not found")
	}

	if method.Type != "email" {
		return "", fmt.Errorf("method is not email")
	}

	// Générer un code à 6 chiffres
	code := fmt.Sprintf("%06d", time.Now().UnixNano()%1000000)

	// Dans un vrai système, on enverrait l'email via un service
	// Ici on simule l'envoi
	fmt.Printf("Email code sent to %s: %s\n", method.Email, code)

	return code, nil
}

// VerifyEmailCode vérifie un code email
func (mfa *MFAManager) VerifyEmailCode(methodID, code string) (bool, error) {
	method, exists := mfa.methods[methodID]
	if !exists {
		return false, fmt.Errorf("method not found")
	}

	if method.Type != "email" {
		return false, fmt.Errorf("method is not email")
	}

	// Dans un vrai système, on vérifierait le code stocké
	// Ici on simule la vérification
	valid := len(code) == 6
	if valid {
		method.IsVerified = true
		method.VerifiedAt = time.Now()
		method.LastUsedAt = time.Now()
	}

	return valid, nil
}

// GetUserMFAMethods récupère toutes les méthodes MFA d'un utilisateur
func (mfa *MFAManager) GetUserMFAMethods(userID string) []*MFAMethod {
	methods := make([]*MFAMethod, 0)

	for _, method := range mfa.methods {
		if method.UserID == userID {
			methods = append(methods, method)
		}
	}

	return methods
}

// ActivateMFAMethod active une méthode MFA
func (mfa *MFAManager) ActivateMFAMethod(methodID string) error {
	method, exists := mfa.methods[methodID]
	if !exists {
		return fmt.Errorf("method not found")
	}

	if !method.IsVerified {
		return fmt.Errorf("method must be verified before activation")
	}

	method.IsActive = true
	return nil
}

// DeactivateMFAMethod désactive une méthode MFA
func (mfa *MFAManager) DeactivateMFAMethod(methodID string) error {
	method, exists := mfa.methods[methodID]
	if !exists {
		return fmt.Errorf("method not found")
	}

	method.IsActive = false
	return nil
}

// DeleteMFAMethod supprime une méthode MFA
func (mfa *MFAManager) DeleteMFAMethod(methodID string) error {
	if _, exists := mfa.methods[methodID]; !exists {
		return fmt.Errorf("method not found")
	}

	delete(mfa.methods, methodID)
	return nil
}

// RequireMFA vérifie si un utilisateur doit utiliser MFA
func (mfa *MFAManager) RequireMFA(userID string) bool {
	methods := mfa.GetUserMFAMethods(userID)

	for _, method := range methods {
		if method.IsActive && method.IsVerified {
			return true
		}
	}

	return false
}

// ValidateMFALogin valide une connexion MFA
func (mfa *MFAManager) ValidateMFALogin(userID, methodID, code string) (bool, error) {
	method, exists := mfa.methods[methodID]
	if !exists {
		return false, fmt.Errorf("method not found")
	}

	if method.UserID != userID {
		return false, fmt.Errorf("method does not belong to user")
	}

	if !method.IsActive || !method.IsVerified {
		return false, fmt.Errorf("method is not active or verified")
	}

	switch method.Type {
	case "totp":
		return mfa.VerifyTOTP(methodID, code)
	case "sms":
		return mfa.VerifySMSCode(methodID, code)
	case "email":
		return mfa.VerifyEmailCode(methodID, code)
	case "backup":
		return mfa.VerifyBackupCode(userID, code)
	default:
		return false, fmt.Errorf("unsupported method type")
	}
}
