package main

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"image/png"
	"math/big"

	"github.com/pquerna/otp"
	"github.com/pquerna/otp/totp"
	"github.com/skip2/go-qrcode"
	"golang.org/x/crypto/bcrypt"
)

// GenerateTOTPSecret génère un nouveau secret TOTP pour un utilisateur.
func GenerateTOTPSecret(issuer, username string) (*otp.Key, error) {
	key, err := totp.Generate(totp.GenerateOpts{
		Issuer:      issuer,
		AccountName: username,
		Period:      30,
		SecretSize:  20,
		Digits:      otp.DigitsSix,
		Algorithm:   otp.AlgorithmSHA1,
	})
	if err != nil {
		return nil, fmt.Errorf("generate TOTP key: %w", err)
	}
	return key, nil
}

// ValidateTOTPCode valide un code TOTP à 6 chiffres.
func ValidateTOTPCode(secret, code string) bool {
	return totp.Validate(code, secret)
}

// GenerateQRCodeDataURL génère une image QR code en data URL base64.
func GenerateQRCodeDataURL(key *otp.Key) (string, error) {
	// Générer l'image QR
	qr, err := qrcode.New(key.URL(), qrcode.Medium)
	if err != nil {
		return "", fmt.Errorf("create QR code: %w", err)
	}

	// Encoder en PNG
	var buf bytes.Buffer
	err = png.Encode(&buf, qr.Image(256))
	if err != nil {
		return "", fmt.Errorf("encode QR code: %w", err)
	}

	// Convertir en data URL
	dataURL := "data:image/png;base64," + base64.StdEncoding.EncodeToString(buf.Bytes())
	return dataURL, nil
}

// GenerateBackupCodes génère 10 codes de secours au format XXXX-XXXX.
func GenerateBackupCodes(count int) ([]string, error) {
	if count <= 0 {
		count = 10
	}

	codes := make([]string, count)
	for i := 0; i < count; i++ {
		// Générer 2 groupes de 4 chiffres
		part1, err := randomDigits(4)
		if err != nil {
			return nil, err
		}
		part2, err := randomDigits(4)
		if err != nil {
			return nil, err
		}
		codes[i] = fmt.Sprintf("%s-%s", part1, part2)
	}
	return codes, nil
}

// randomDigits génère une chaîne de n chiffres aléatoires.
func randomDigits(n int) (string, error) {
	max := new(big.Int)
	max.Exp(big.NewInt(10), big.NewInt(int64(n)), nil)

	num, err := rand.Int(rand.Reader, max)
	if err != nil {
		return "", fmt.Errorf("generate random digits: %w", err)
	}

	format := fmt.Sprintf("%%0%dd", n)
	return fmt.Sprintf(format, num), nil
}

// HashBackupCodes hache tous les codes de secours avec bcrypt.
func HashBackupCodes(codes []string) ([]string, error) {
	hashed := make([]string, len(codes))
	for i, code := range codes {
		hash, err := bcrypt.GenerateFromPassword([]byte(code), bcrypt.DefaultCost)
		if err != nil {
			return nil, fmt.Errorf("hash backup code: %w", err)
		}
		hashed[i] = string(hash)
	}
	return hashed, nil
}

// VerifyBackupCode vérifie un code de secours contre une liste de hashes.
// Retourne l'index du code trouvé ou -1 si non trouvé.
func VerifyBackupCode(code string, hashedCodes []string) int {
	for i, hash := range hashedCodes {
		if bcrypt.CompareHashAndPassword([]byte(hash), []byte(code)) == nil {
			return i
		}
	}
	return -1
}

// RemoveBackupCode supprime un code de secours de la liste (après utilisation).
func RemoveBackupCode(codes []string, index int) []string {
	if index < 0 || index >= len(codes) {
		return codes
	}
	return append(codes[:index], codes[index+1:]...)
}

// FormatBackupCodes formate les codes pour affichage (2 colonnes).
func FormatBackupCodes(codes []string) string {
	var buf bytes.Buffer
	for i, code := range codes {
		buf.WriteString(code)
		if i%2 == 0 && i < len(codes)-1 {
			buf.WriteString("    ")
		} else {
			buf.WriteString("\n")
		}
	}
	return buf.String()
}
