// Package auth — Authentification PAM via SSH.
// Au lieu de monter les fichiers système du host (/etc/shadow), on tente une connexion
// SSH avec les credentials de l'utilisateur. Si elle réussit, les credentials sont valides.
// L'appartenance au groupe sudo/wheel détermine le niveau admin.
package auth

import (
	"fmt"
	"log"
	"net"
	"strings"
	"time"

	"golang.org/x/crypto/ssh"
)

// UserInfo contient les informations d'un utilisateur authentifié.
type UserInfo struct {
	Username string
	IsAdmin  bool
}

// SSHAuthenticator gère l'authentification des utilisateurs via SSH vers le host Proxmox.
type SSHAuthenticator struct {
	host string // ex: "10.0.0.1:2244"
}

// NewSSHAuthenticator crée un authentificateur SSH pour le host donné.
func NewSSHAuthenticator(host string) *SSHAuthenticator {
	return &SSHAuthenticator{host: host}
}

// Authenticate tente une connexion SSH avec les credentials fournis.
// Si la connexion réussit, retourne les informations de l'utilisateur.
// Vérifie l'appartenance au groupe sudo ou wheel pour déterminer le niveau admin.
func (a *SSHAuthenticator) Authenticate(username, password string) (*UserInfo, error) {
	config := &ssh.ClientConfig{
		User: username,
		Auth: []ssh.AuthMethod{
			ssh.Password(password),
		},
		// Timeout court pour l'authentification
		Timeout: 10 * time.Second,
		// Accepter n'importe quelle clé host (le host est sur le réseau interne de confiance)
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	// Tentative de connexion SSH
	log.Printf("[ssh_auth] Dial %s@%s...", username, a.host)
	client, err := ssh.Dial("tcp", a.host, config)
	if err != nil {
		log.Printf("[ssh_auth] Échec dial %s@%s : %v", username, a.host, err)
		// Distinguer les erreurs d'authentification des erreurs réseau
		if strings.Contains(err.Error(), "unable to authenticate") ||
			strings.Contains(err.Error(), "ssh: handshake failed") ||
			strings.Contains(err.Error(), "no supported methods remain") {
			return nil, fmt.Errorf("identifiants invalides")
		}
		return nil, fmt.Errorf("connexion SSH impossible : %w", err)
	}
	defer client.Close()
	log.Printf("[ssh_auth] Connexion établie — %s@%s", username, a.host)

	// Vérifier l'appartenance aux groupes sudo/wheel via la commande `id`
	isAdmin, err := checkSudoGroup(client)
	if err != nil {
		log.Printf("[ssh_auth] Avertissement vérification sudo — user=%s : %v", username, err)
		isAdmin = false
	}
	log.Printf("[ssh_auth] Authentifié — user=%s admin=%v", username, isAdmin)

	return &UserInfo{
		Username: username,
		IsAdmin:  isAdmin,
	}, nil
}

// TestConnectivity teste la connexion SSH sans authentification complète.
// Utilisé pendant l'installation pour valider les paramètres de connexion.
func TestConnectivity(host string, timeout time.Duration) error {
	conn, err := net.DialTimeout("tcp", host, timeout)
	if err != nil {
		return fmt.Errorf("impossible de joindre %s : %w", host, err)
	}
	conn.Close()
	return nil
}

// TestSSHAuth teste une connexion SSH complète avec credentials.
// Retourne nil si la connexion réussit, une erreur explicite sinon.
func TestSSHAuth(host, username, password string) error {
	config := &ssh.ClientConfig{
		User: username,
		Auth: []ssh.AuthMethod{
			ssh.Password(password),
		},
		Timeout:         10 * time.Second,
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	client, err := ssh.Dial("tcp", host, config)
	if err != nil {
		if strings.Contains(err.Error(), "unable to authenticate") {
			return fmt.Errorf("identifiants SSH invalides")
		}
		return fmt.Errorf("connexion SSH échouée : %w", err)
	}
	client.Close()
	return nil
}

// checkSudoGroup exécute `id -nG` sur la session SSH et vérifie la présence
// des groupes "sudo" ou "wheel" dans la liste des groupes de l'utilisateur.
func checkSudoGroup(client *ssh.Client) (bool, error) {
	session, err := client.NewSession()
	if err != nil {
		return false, fmt.Errorf("ouverture session SSH : %w", err)
	}
	defer session.Close()

	output, err := session.Output("id -nG")
	if err != nil {
		return false, fmt.Errorf("exécution `id -nG` : %w", err)
	}

	groups := strings.Fields(strings.TrimSpace(string(output)))
	for _, g := range groups {
		if g == "sudo" || g == "wheel" {
			return true, nil
		}
	}

	return false, nil
}
