package modules

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"

	"git.geronzi.fr/proxmoxPanel/core/backend/internal/crypto"
	"git.geronzi.fr/proxmoxPanel/core/backend/internal/db"
	sshpool "git.geronzi.fr/proxmoxPanel/core/backend/internal/ssh"
)

// Loader charge et gère les modules actifs.
type Loader struct {
	db       *db.DB
	pool     *sshpool.Pool
	enc      *crypto.Encryptor
	registry *coreRegistry
	modules  []Module
}

// NewLoader crée un Loader avec les services du CORE nécessaires aux modules.
func NewLoader(database *db.DB, pool *sshpool.Pool, enc *crypto.Encryptor) *Loader {
	return &Loader{
		db:       database,
		pool:     pool,
		enc:      enc,
		registry: newCoreRegistry(database.DB, pool, enc),
	}
}

// RegisterModule enregistre un module disponible (appelé à l'init, depuis main.go).
func (l *Loader) RegisterModule(m Module) {
	l.modules = append(l.modules, m)
}

// LoadActive charge et initialise tous les modules activés en base de données.
func (l *Loader) LoadActive() error {
	for _, m := range l.modules {
		enabled, err := l.isEnabled(m.ID())
		if err != nil {
			return fmt.Errorf("vérification module %s : %w", m.ID(), err)
		}
		if !enabled {
			log.Printf("Module %s : désactivé, ignoré", m.ID())
			continue
		}

		log.Printf("Module %s : chargement...", m.ID())
		if err := m.Register(l.registry); err != nil {
			return fmt.Errorf("initialisation module %s : %w", m.ID(), err)
		}
		log.Printf("Module %s : chargé avec succès", m.ID())
	}
	return nil
}

// isEnabled vérifie en base de données si un module est activé.
func (l *Loader) isEnabled(id string) (bool, error) {
	var enabled int
	err := l.db.QueryRow(`SELECT is_enabled FROM modules WHERE id = ?`, id).Scan(&enabled)
	if err == sql.ErrNoRows {
		return false, nil
	}
	return enabled == 1, err
}

// Registry retourne le registry partagé.
func (l *Loader) Registry() *coreRegistry {
	return l.registry
}

// ---- Implémentation interne du Registry ----

type RouteEntry struct {
	Method       string
	Path         string
	Handler      http.HandlerFunc
	RequireAdmin bool
}

type migrationEntry struct {
	version int
	sql     string
	fn      MigrationFn
}

type translationEntry struct {
	lang string
	keys map[string]string
}

// coreRegistry implémente l'interface Registry.
type coreRegistry struct {
	sqlDB        *sql.DB
	pool         *sshpool.Pool
	enc          *crypto.Encryptor
	routes       []RouteEntry
	wsChannels   map[string]WSHandler
	widgets      []WidgetDef
	settingsTabs []SettingsTabDef
	migrations   []migrationEntry
	translations []translationEntry
	navItems     map[string]NavItemDef // nav items en mémoire (clé = module ID)
}

func newCoreRegistry(sqlDB *sql.DB, pool *sshpool.Pool, enc *crypto.Encryptor) *coreRegistry {
	return &coreRegistry{
		sqlDB:      sqlDB,
		pool:       pool,
		enc:        enc,
		wsChannels: make(map[string]WSHandler),
		navItems:   make(map[string]NavItemDef),
	}
}

func (r *coreRegistry) RegisterRoute(method, path string, handler http.HandlerFunc, requireAdmin bool) {
	r.routes = append(r.routes, RouteEntry{method, path, handler, requireAdmin})
}

func (r *coreRegistry) RegisterWSChannel(channel string, handler WSHandler) {
	r.wsChannels[channel] = handler
}

func (r *coreRegistry) RegisterWidget(widget WidgetDef) {
	r.widgets = append(r.widgets, widget)
}

func (r *coreRegistry) RegisterSettingsTab(tab SettingsTabDef) {
	r.settingsTabs = append(r.settingsTabs, tab)
}

func (r *coreRegistry) RegisterTranslations(lang string, keys map[string]string) {
	r.translations = append(r.translations, translationEntry{lang, keys})
}

func (r *coreRegistry) RegisterMigration(version int, sqlStr string, fn MigrationFn) {
	r.migrations = append(r.migrations, migrationEntry{version, sqlStr, fn})
}

// RegisterNavItem enregistre l'entrée de navigation d'un module en mémoire et en DB.
func (r *coreRegistry) RegisterNavItem(item NavItemDef) {
	r.navItems[item.ID] = item
	// Persister en DB pour que le frontend puisse le récupérer via /api/modules
	r.sqlDB.Exec(
		`UPDATE modules SET nav_href=?, nav_icon=?, nav_color=?, nav_label_key=? WHERE id=?`,
		item.Href, item.Icon, item.Color, item.LabelKey, item.ID,
	)
}

// DB retourne la connexion SQLite brute.
func (r *coreRegistry) DB() *sql.DB {
	return r.sqlDB
}

// RunOnTarget exécute une commande SSH sur la cible (host ou lxc:VMID).
// La commande est wrappée via pct exec pour les cibles LXC.
func (r *coreRegistry) RunOnTarget(target, command string) (string, error) {
	host, user, pass, err := r.sshCreds()
	if err != nil {
		return "", err
	}
	cmd := buildTargetCmd(target, command)
	return r.pool.RunCommand(host, user, pass, cmd)
}

// StreamOnTarget exécute une commande SSH en streaming sur la cible.
func (r *coreRegistry) StreamOnTarget(target, command string, output chan<- string) error {
	host, user, pass, err := r.sshCreds()
	if err != nil {
		return err
	}
	cmd := buildTargetCmd(target, command)
	return r.pool.StreamCommand(host, user, pass, cmd, output)
}

// sshCreds récupère et déchiffre les credentials SSH depuis la configuration.
func (r *coreRegistry) sshCreds() (host, user, pass string, err error) {
	var h, u, ep string
	r.sqlDB.QueryRow(`SELECT value FROM settings WHERE key='ssh_host'`).Scan(&h)
	r.sqlDB.QueryRow(`SELECT value FROM settings WHERE key='ssh_username'`).Scan(&u)
	r.sqlDB.QueryRow(`SELECT value FROM settings WHERE key='ssh_password'`).Scan(&ep)
	if ep != "" {
		pass, err = r.enc.Decrypt(ep)
		if err != nil {
			return "", "", "", fmt.Errorf("impossible de déchiffrer le mot de passe SSH")
		}
	}
	if h == "" || u == "" || pass == "" {
		return "", "", "", fmt.Errorf("SSH non configuré")
	}
	return h, u, pass, nil
}

// buildTargetCmd construit la commande pour la cible (host ou lxc:VMID).
func buildTargetCmd(target, command string) string {
	if strings.HasPrefix(target, "lxc:") {
		vmid := strings.TrimPrefix(target, "lxc:")
		if _, err := strconv.Atoi(vmid); err == nil {
			return fmt.Sprintf("pct exec %s -- sh -c %q", vmid, command)
		}
	}
	return command
}

// GetRoutes retourne les routes enregistrées par les modules.
func (r *coreRegistry) GetRoutes() []RouteEntry {
	return r.routes
}

// GetNavItems retourne les nav items enregistrés en mémoire.
func (r *coreRegistry) GetNavItems() map[string]NavItemDef {
	return r.navItems
}

// GetWidgets retourne les types de widgets disponibles.
func (r *coreRegistry) GetWidgets() []WidgetDef {
	return r.widgets
}

// GetSettingsTabs retourne les onglets de paramètres des modules.
func (r *coreRegistry) GetSettingsTabs() []SettingsTabDef {
	return r.settingsTabs
}
