package main

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"gopkg.in/yaml.v3"
	"sogoms.com/internal/protocol"
)

// ServicePool centralise les connexions vers les microservices.
type ServicePool struct {
	DB   *protocol.Pool
	Logs *protocol.Pool
	Cron *protocol.Pool
}

// ServiceStatus représente le statut d'un service.
type ServiceStatus struct {
	Name      string `json:"name"`
	Available bool   `json:"available"`
	LatencyMs int64  `json:"latency_ms"`
}

// HealthCheck vérifie l'état de tous les services.
func (sp *ServicePool) HealthCheck() []ServiceStatus {
	statuses := make([]ServiceStatus, 0, 3)

	// Check sogoms-db
	if sp.DB != nil {
		statuses = append(statuses, sp.checkService("sogoms-db", sp.DB))
	}

	// Check sogoms-logs
	if sp.Logs != nil {
		statuses = append(statuses, sp.checkService("sogoms-logs", sp.Logs))
	}

	// Check sogoms-cron
	if sp.Cron != nil {
		statuses = append(statuses, sp.checkService("sogoms-cron", sp.Cron))
	}

	return statuses
}

// checkService vérifie un service individuel.
func (sp *ServicePool) checkService(name string, pool *protocol.Pool) ServiceStatus {
	status := ServiceStatus{
		Name:      name,
		Available: false,
	}

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()

	start := time.Now()
	req := protocol.NewRequest("health", nil)
	resp, err := pool.Call(ctx, req)

	status.LatencyMs = time.Since(start).Milliseconds()

	if err == nil && resp != nil && resp.Status == "success" {
		status.Available = true
	}

	return status
}

// GetCronJobs récupère la liste des jobs cron.
func (sp *ServicePool) GetCronJobs() ([]map[string]any, error) {
	if sp.Cron == nil {
		return nil, nil
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	req := protocol.NewRequest("list", nil)
	resp, err := sp.Cron.Call(ctx, req)
	if err != nil {
		return nil, err
	}

	if resp.Status != "success" {
		return nil, nil
	}

	// Extraire les jobs
	if result, ok := resp.Result.(map[string]any); ok {
		if jobs, ok := result["jobs"].([]any); ok {
			jobList := make([]map[string]any, 0, len(jobs))
			for _, j := range jobs {
				if job, ok := j.(map[string]any); ok {
					jobList = append(jobList, job)
				}
			}
			return jobList, nil
		}
	}

	return nil, nil
}

// TriggerCronJob déclenche un job cron manuellement.
func (sp *ServicePool) TriggerCronJob(appID, jobName string) error {
	if sp.Cron == nil {
		return nil
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	req := protocol.NewRequest("trigger", map[string]any{
		"app_id": appID,
		"job":    jobName,
	})
	_, err := sp.Cron.Call(ctx, req)
	return err
}

// GetCronHistory récupère l'historique des exécutions cron.
func (sp *ServicePool) GetCronHistory(appID string, limit int) ([]map[string]any, error) {
	if sp.Cron == nil {
		return nil, nil
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	params := map[string]any{"limit": limit}
	if appID != "" {
		params["app_id"] = appID
	}

	req := protocol.NewRequest("status", params)
	resp, err := sp.Cron.Call(ctx, req)
	if err != nil {
		return nil, err
	}

	if resp.Status != "success" {
		return nil, nil
	}

	// Extraire les exécutions
	if result, ok := resp.Result.(map[string]any); ok {
		if execs, ok := result["executions"].([]any); ok {
			execList := make([]map[string]any, 0, len(execs))
			for _, e := range execs {
				if exec, ok := e.(map[string]any); ok {
					execList = append(execList, exec)
				}
			}
			return execList, nil
		}
	}

	return nil, nil
}

// CreateApp crée une nouvelle application avec sa configuration.
func (sp *ServicePool) CreateApp(appName, version, basePath string, hosts []string, dbHost, dbPort, dbUser, dbName, dbPassword, jwtExpiry string) error {
	configDir := "/config"
	secretsDir := "/secrets"

	// Créer le dossier de l'app
	appDir := filepath.Join(configDir, "apps", appName)
	if err := os.MkdirAll(appDir, 0755); err != nil {
		return fmt.Errorf("mkdir app: %w", err)
	}

	// Générer le JWT secret
	jwtSecret := make([]byte, 32)
	if _, err := rand.Read(jwtSecret); err != nil {
		return fmt.Errorf("generate jwt secret: %w", err)
	}
	jwtSecretB64 := base64.StdEncoding.EncodeToString(jwtSecret)

	// Créer les fichiers secrets
	dbPassFile := filepath.Join(secretsDir, appName+"_db_pass")
	if err := os.WriteFile(dbPassFile, []byte(dbPassword), 0600); err != nil {
		return fmt.Errorf("write db password: %w", err)
	}

	jwtSecretFile := filepath.Join(secretsDir, appName+"_jwt_secret")
	if err := os.WriteFile(jwtSecretFile, []byte(jwtSecretB64), 0600); err != nil {
		return fmt.Errorf("write jwt secret: %w", err)
	}

	// Générer app.yaml
	hostsYAML := ""
	for _, h := range hosts {
		hostsYAML += fmt.Sprintf("  - %s\n", h)
	}

	appYAML := fmt.Sprintf(`# Application %s
# Générée automatiquement par sogoms-admin

app: %s
version: "%s"
base_path: %s

hosts:
%s
database:
  host: %s
  port: %s
  user: %s
  password_file: %s
  name: %s

auth:
  jwt_secret_file: %s
  jwt_expiry: %s

logs:
  retention_days: 30

routes: []
`, appName, appName, version, basePath, hostsYAML, dbHost, dbPort, dbUser, dbPassFile, dbName, jwtSecretFile, jwtExpiry)

	appYAMLFile := filepath.Join(appDir, "app.yaml")
	if err := os.WriteFile(appYAMLFile, []byte(appYAML), 0644); err != nil {
		return fmt.Errorf("write app.yaml: %w", err)
	}

	return nil
}

// ScanAndGenerateSchema introspect la DB et génère schema.yaml.
// Retourne le nombre de tables détectées.
func (sp *ServicePool) ScanAndGenerateSchema(appID string) (int, error) {
	if sp.DB == nil {
		return 0, fmt.Errorf("db service not available")
	}

	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// Appeler l'introspection
	req := protocol.NewRequest("introspect", map[string]any{
		"app_id": appID,
	})
	resp, err := sp.DB.Call(ctx, req)
	if err != nil {
		return 0, fmt.Errorf("introspect call: %w", err)
	}

	if resp.Status != "success" {
		if resp.Error != nil {
			return 0, fmt.Errorf("introspect failed: %s", resp.Error.Message)
		}
		return 0, fmt.Errorf("introspect failed")
	}

	// Extraire les tables du résultat
	result, ok := resp.Result.(map[string]any)
	if !ok {
		return 0, fmt.Errorf("invalid introspect result")
	}

	tablesRaw, ok := result["tables"].(map[string]any)
	if !ok {
		return 0, fmt.Errorf("no tables in result")
	}

	tableCount := len(tablesRaw)

	// Construire le schema
	schema := map[string]any{
		"app":    appID,
		"tables": convertTablesToSchema(tablesRaw),
	}

	// Sérialiser en YAML
	yamlData, err := yaml.Marshal(schema)
	if err != nil {
		return 0, fmt.Errorf("yaml marshal: %w", err)
	}

	// Écrire le fichier
	schemaFile := filepath.Join("/config", "apps", appID, "schema.yaml")
	if err := os.WriteFile(schemaFile, yamlData, 0644); err != nil {
		return 0, fmt.Errorf("write schema.yaml: %w", err)
	}

	return tableCount, nil
}

// convertTablesToSchema convertit les données d'introspection en format schema.
func convertTablesToSchema(tablesRaw map[string]any) map[string]any {
	tables := make(map[string]any)

	// Trier les tables par nom pour un output cohérent
	tableNames := make([]string, 0, len(tablesRaw))
	for name := range tablesRaw {
		tableNames = append(tableNames, name)
	}
	sort.Strings(tableNames)

	// Première passe : créer toutes les tables
	for _, tableName := range tableNames {
		tableData, ok := tablesRaw[tableName].(map[string]any)
		if !ok {
			continue
		}

		table := make(map[string]any)

		// Colonnes
		if columnsRaw, ok := tableData["columns"].(map[string]any); ok {
			columns := make(map[string]any)

			// Trier les colonnes
			colNames := make([]string, 0, len(columnsRaw))
			for name := range columnsRaw {
				colNames = append(colNames, name)
			}
			sort.Strings(colNames)

			for _, colName := range colNames {
				colData, ok := columnsRaw[colName].(map[string]any)
				if !ok {
					continue
				}

				col := make(map[string]any)

				// Type
				if t, ok := colData["type"].(string); ok {
					col["type"] = t
				}

				// Longueur
				if l, ok := colData["length"].(float64); ok && l > 0 {
					col["length"] = int(l)
				}

				// Primary
				if p, ok := colData["primary"].(bool); ok && p {
					col["primary"] = true
				}

				// Auto increment
				if a, ok := colData["auto"].(bool); ok && a {
					col["auto"] = true
				}

				// Required (NOT NULL)
				if r, ok := colData["required"].(bool); ok && r {
					col["required"] = true
				}

				// Default
				if d, ok := colData["default"].(string); ok && d != "" {
					col["default"] = d
				}

				// Unique
				if u, ok := colData["unique"].(bool); ok && u {
					col["unique"] = true
				}

				// Foreign key
				if fk, ok := colData["foreign"].(string); ok && fk != "" {
					col["foreign"] = fk
				}

				// Détecter filter: owner pour les colonnes user_id
				if colName == "user_id" {
					col["filter"] = "owner"
				}

				columns[colName] = col
			}
			table["columns"] = columns
		}

		// Primary keys
		if pk, ok := tableData["primary"].([]any); ok && len(pk) > 0 {
			pkStrings := make([]string, 0, len(pk))
			for _, p := range pk {
				if s, ok := p.(string); ok {
					pkStrings = append(pkStrings, s)
				}
			}
			table["primary"] = pkStrings
		}

		// Détecter soft_delete (colonne deleted_at)
		if hasSoftDelete(tableData) {
			table["soft_delete"] = true
		}

		// CRUD par défaut (sauf tables de liaison)
		if hasUserID(tableData) {
			table["crud"] = []string{"list", "show", "create", "update", "delete"}
		} else {
			table["crud"] = []string{}
		}

		tables[tableName] = table
	}

	// Deuxième passe : détecter cascade sur les tables parent
	// Une table parent a cascade si elle a soft_delete ET des tables enfants avec soft_delete
	for parentName, parentTable := range tables {
		parent, ok := parentTable.(map[string]any)
		if !ok {
			continue
		}

		// Si la table parent n'a pas soft_delete, pas de cascade
		if sd, ok := parent["soft_delete"].(bool); !ok || !sd {
			continue
		}

		// Chercher si des tables enfants ont une FK vers cette table
		hasChildWithSoftDelete := false
		for childName, childTable := range tables {
			if childName == parentName {
				continue
			}
			child, ok := childTable.(map[string]any)
			if !ok {
				continue
			}

			// Vérifier si l'enfant a soft_delete
			childSD, _ := child["soft_delete"].(bool)
			if !childSD {
				continue
			}

			// Vérifier si l'enfant a une FK vers le parent
			if cols, ok := child["columns"].(map[string]any); ok {
				for _, colData := range cols {
					if col, ok := colData.(map[string]any); ok {
						if fk, ok := col["foreign"].(string); ok {
							// fk = "table.column"
							if strings.HasPrefix(fk, parentName+".") {
								hasChildWithSoftDelete = true
								break
							}
						}
					}
				}
			}
			if hasChildWithSoftDelete {
				break
			}
		}

		if hasChildWithSoftDelete {
			parent["cascade"] = true
		}
	}

	return tables
}

// hasUserID vérifie si une table a une colonne user_id.
func hasUserID(tableData map[string]any) bool {
	if columnsRaw, ok := tableData["columns"].(map[string]any); ok {
		_, hasIt := columnsRaw["user_id"]
		return hasIt
	}
	return false
}

// hasSoftDelete vérifie si une table a une colonne deleted_at (TIMESTAMP/DATETIME).
func hasSoftDelete(tableData map[string]any) bool {
	if columnsRaw, ok := tableData["columns"].(map[string]any); ok {
		if col, ok := columnsRaw["deleted_at"].(map[string]any); ok {
			if colType, ok := col["type"].(string); ok && colType == "datetime" {
				return true
			}
		}
	}
	return false
}

// UpdateLoginData met à jour le bloc login_data dans auth.yaml
// en se basant sur le schema généré (tables avec filter: owner).
func UpdateLoginData(appID string) error {
	// 1. Lire le schema.yaml
	schemaPath := filepath.Join("/config", "apps", appID, "schema.yaml")
	schemaData, err := os.ReadFile(schemaPath)
	if err != nil {
		return fmt.Errorf("read schema: %w", err)
	}

	var schema map[string]any
	if err := yaml.Unmarshal(schemaData, &schema); err != nil {
		return fmt.Errorf("parse schema: %w", err)
	}

	// 2. Identifier les tables avec user_id (filter: owner)
	tablesRaw, ok := schema["tables"].(map[string]any)
	if !ok {
		return nil // Pas de tables, rien à faire
	}

	loginData := make(map[string]string)
	tableNames := make([]string, 0, len(tablesRaw))
	for name := range tablesRaw {
		tableNames = append(tableNames, name)
	}
	sort.Strings(tableNames)

	for _, tableName := range tableNames {
		tableRaw := tablesRaw[tableName]
		table, ok := tableRaw.(map[string]any)
		if !ok {
			continue
		}

		// Vérifier si la table a une colonne avec filter: owner
		columns, ok := table["columns"].(map[string]any)
		if !ok {
			continue
		}

		hasOwnerFilter := false
		for _, colRaw := range columns {
			col, ok := colRaw.(map[string]any)
			if !ok {
				continue
			}
			if filter, ok := col["filter"].(string); ok && filter == "owner" {
				hasOwnerFilter = true
				break
			}
		}

		if !hasOwnerFilter {
			continue
		}

		// Collecter les noms de colonnes (sauf user_id)
		colNames := make([]string, 0, len(columns))
		hasPosition := false
		for colName := range columns {
			if colName == "user_id" {
				continue // On n'inclut pas user_id dans le SELECT
			}
			colNames = append(colNames, colName)
			if colName == "position" {
				hasPosition = true
			}
		}
		sort.Strings(colNames)

		// Mettre id en premier si présent
		for i, name := range colNames {
			if name == "id" {
				colNames = append([]string{"id"}, append(colNames[:i], colNames[i+1:]...)...)
				break
			}
		}

		// Construire la requête
		query := fmt.Sprintf("SELECT %s\nFROM %s WHERE user_id = ?",
			strings.Join(colNames, ", "), tableName)

		// Ajouter ORDER BY si position existe
		if hasPosition {
			query += " ORDER BY position"
		}

		loginData[tableName] = query
	}

	if len(loginData) == 0 {
		return nil // Pas de tables owner, rien à générer
	}

	// 3. Lire auth.yaml existant
	authPath := filepath.Join("/config", "apps", appID, "queries", "auth.yaml")
	var existingData map[string]any

	if data, err := os.ReadFile(authPath); err == nil {
		if err := yaml.Unmarshal(data, &existingData); err != nil {
			existingData = make(map[string]any)
		}
	} else {
		existingData = make(map[string]any)
	}

	// 4. Mettre à jour seulement login_data
	existingData["login_data"] = loginData

	// 5. Réécrire le fichier avec commentaire
	queriesDir := filepath.Dir(authPath)
	if err := os.MkdirAll(queriesDir, 0755); err != nil {
		return fmt.Errorf("create queries dir: %w", err)
	}

	yamlData, err := yaml.Marshal(existingData)
	if err != nil {
		return fmt.Errorf("marshal auth.yaml: %w", err)
	}

	// Ajouter un header
	header := "# Requêtes d'authentification\n# login_data généré automatiquement depuis schema.yaml\n\n"
	finalData := []byte(header + string(yamlData))

	if err := os.WriteFile(authPath, finalData, 0644); err != nil {
		return fmt.Errorf("write auth.yaml: %w", err)
	}

	return nil
}

// GenerateRoutesFromSchema génère les routes CRUD dans app.yaml basées sur schema.yaml.
func GenerateRoutesFromSchema(appID string) error {
	// 1. Lire le schema.yaml
	schemaPath := filepath.Join("/config", "apps", appID, "schema.yaml")
	schemaData, err := os.ReadFile(schemaPath)
	if err != nil {
		return fmt.Errorf("read schema: %w", err)
	}

	var schema map[string]any
	if err := yaml.Unmarshal(schemaData, &schema); err != nil {
		return fmt.Errorf("parse schema: %w", err)
	}

	// 2. Lire app.yaml existant
	appPath := filepath.Join("/config", "apps", appID, "app.yaml")
	appData, err := os.ReadFile(appPath)
	if err != nil {
		return fmt.Errorf("read app.yaml: %w", err)
	}

	var appConfig map[string]any
	if err := yaml.Unmarshal(appData, &appConfig); err != nil {
		return fmt.Errorf("parse app.yaml: %w", err)
	}

	// 3. Extraire les tables avec CRUD
	tablesRaw, ok := schema["tables"].(map[string]any)
	if !ok {
		return nil // Pas de tables
	}

	// Trier les tables
	tableNames := make([]string, 0, len(tablesRaw))
	for name := range tablesRaw {
		tableNames = append(tableNames, name)
	}
	sort.Strings(tableNames)

	// 4. Générer les routes
	routes := []map[string]any{
		// Routes auth par défaut
		{"path": "/auth/register", "method": "POST", "scenario": appID + "/auth/register", "auth": false},
		{"path": "/auth/login", "method": "POST", "scenario": appID + "/auth/login", "auth": false},
		{"path": "/auth/logout", "method": "POST", "scenario": appID + "/auth/logout"},
		{"path": "/auth/me", "method": "GET", "scenario": appID + "/auth/me"},
	}

	for _, tableName := range tableNames {
		tableRaw := tablesRaw[tableName]
		table, ok := tableRaw.(map[string]any)
		if !ok {
			continue
		}

		// Vérifier si CRUD est défini
		crudRaw, ok := table["crud"].([]any)
		if !ok || len(crudRaw) == 0 {
			continue
		}

		// Convertir en map pour lookup rapide
		crudOps := make(map[string]bool)
		for _, op := range crudRaw {
			if opStr, ok := op.(string); ok {
				crudOps[opStr] = true
			}
		}

		// Générer les routes pour cette table
		if crudOps["list"] {
			routes = append(routes, map[string]any{
				"path":     "/" + tableName,
				"method":   "GET",
				"scenario": appID + "/" + tableName + "/list",
			})
		}
		if crudOps["create"] {
			routes = append(routes, map[string]any{
				"path":     "/" + tableName,
				"method":   "POST",
				"scenario": appID + "/" + tableName + "/create",
			})
		}
		if crudOps["show"] {
			routes = append(routes, map[string]any{
				"path":     "/" + tableName + "/{id}",
				"method":   "GET",
				"scenario": appID + "/" + tableName + "/show",
			})
		}
		if crudOps["update"] {
			routes = append(routes, map[string]any{
				"path":     "/" + tableName + "/{id}",
				"method":   "PUT",
				"scenario": appID + "/" + tableName + "/update",
			})
		}
		if crudOps["delete"] {
			routes = append(routes, map[string]any{
				"path":     "/" + tableName + "/{id}",
				"method":   "DELETE",
				"scenario": appID + "/" + tableName + "/delete",
			})
		}
	}

	// 5. Mettre à jour app.yaml
	appConfig["routes"] = routes

	// 6. Réécrire le fichier
	yamlData, err := yaml.Marshal(appConfig)
	if err != nil {
		return fmt.Errorf("marshal app.yaml: %w", err)
	}

	header := fmt.Sprintf("# Application %s\n# Routes générées automatiquement depuis schema.yaml\n\n", appID)
	if err := os.WriteFile(appPath, []byte(header+string(yamlData)), 0644); err != nil {
		return fmt.Errorf("write app.yaml: %w", err)
	}

	return nil
}

// GenerateQueriesFromSchema génère les fichiers queries/*.yaml basés sur schema.yaml.
func GenerateQueriesFromSchema(appID string) error {
	// 1. Lire le schema.yaml
	schemaPath := filepath.Join("/config", "apps", appID, "schema.yaml")
	schemaData, err := os.ReadFile(schemaPath)
	if err != nil {
		return fmt.Errorf("read schema: %w", err)
	}

	var schema map[string]any
	if err := yaml.Unmarshal(schemaData, &schema); err != nil {
		return fmt.Errorf("parse schema: %w", err)
	}

	// 2. Créer le dossier queries
	queriesDir := filepath.Join("/config", "apps", appID, "queries")
	if err := os.MkdirAll(queriesDir, 0755); err != nil {
		return fmt.Errorf("create queries dir: %w", err)
	}

	// 3. Extraire les tables
	tablesRaw, ok := schema["tables"].(map[string]any)
	if !ok {
		return nil
	}

	for tableName, tableRaw := range tablesRaw {
		table, ok := tableRaw.(map[string]any)
		if !ok {
			continue
		}

		// Vérifier si CRUD est défini
		crudRaw, ok := table["crud"].([]any)
		if !ok || len(crudRaw) == 0 {
			continue
		}

		// Générer le fichier queries pour cette table
		if err := generateTableQueries(queriesDir, tableName, table); err != nil {
			return fmt.Errorf("generate queries for %s: %w", tableName, err)
		}
	}

	return nil
}

// generateTableQueries génère le fichier queries pour une table.
func generateTableQueries(queriesDir, tableName string, table map[string]any) error {
	columns, ok := table["columns"].(map[string]any)
	if !ok {
		return nil
	}

	// Collecter les colonnes
	colNames := make([]string, 0, len(columns))
	createFields := make([]string, 0, len(columns))
	updateFields := make([]string, 0, len(columns))
	hasPosition := false
	hasUserID := false

	for colName, colRaw := range columns {
		col, ok := colRaw.(map[string]any)
		if !ok {
			continue
		}

		colNames = append(colNames, colName)

		if colName == "position" {
			hasPosition = true
		}
		if colName == "user_id" {
			hasUserID = true
		}

		// Exclure les colonnes auto-générées du CREATE
		isAuto, _ := col["auto"].(bool)
		isPrimary, _ := col["primary"].(bool)
		isAutoGenerated := colName == "created_at" || colName == "updated_at" || colName == "deleted_at"

		if !isAuto && !isAutoGenerated {
			createFields = append(createFields, colName)
		}

		// Exclure id, user_id et auto-générées de l'UPDATE
		if !isPrimary && !isAuto && !isAutoGenerated && colName != "user_id" {
			updateFields = append(updateFields, colName)
		}
	}

	sort.Strings(colNames)
	sort.Strings(createFields)
	sort.Strings(updateFields)

	// Mettre id en premier dans les colonnes SELECT
	selectCols := make([]string, 0, len(colNames))
	for _, name := range colNames {
		if name == "id" {
			selectCols = append([]string{"id"}, selectCols...)
		} else if name != "user_id" { // Exclure user_id du SELECT
			selectCols = append(selectCols, name)
		}
	}

	// Construire le contenu YAML
	queries := make(map[string]any)

	// LIST
	listQuery := fmt.Sprintf("SELECT %s\nFROM %s", strings.Join(selectCols, ", "), tableName)
	orderBy := ""
	if hasPosition {
		orderBy = "position ASC"
	}

	listConfig := map[string]any{
		"query": listQuery,
	}
	if hasUserID {
		listConfig["filters"] = map[string]string{
			"default": "user_id = :user_id",
			"admin":   "",
		}
	}
	if orderBy != "" {
		listConfig["order"] = orderBy
	}
	queries["list"] = listConfig

	// SHOW
	showQuery := fmt.Sprintf("SELECT %s\nFROM %s WHERE id = :id", strings.Join(selectCols, ", "), tableName)
	showConfig := map[string]any{
		"query": showQuery,
	}
	if hasUserID {
		showConfig["filters"] = map[string]string{
			"default": "user_id = :user_id",
			"admin":   "",
		}
	}
	queries["show"] = showConfig

	// CREATE
	queries["create"] = map[string]any{
		"table":  tableName,
		"fields": createFields,
	}

	// UPDATE
	updateConfig := map[string]any{
		"table":  tableName,
		"fields": updateFields,
	}
	if hasUserID {
		updateConfig["filters"] = map[string]string{
			"default": "user_id = :user_id",
			"admin":   "",
		}
	}
	queries["update"] = updateConfig

	// DELETE
	deleteConfig := map[string]any{
		"table": tableName,
	}
	if hasUserID {
		deleteConfig["filters"] = map[string]string{
			"default": "user_id = :user_id",
			"admin":   "",
		}
	}
	queries["delete"] = deleteConfig

	// Sérialiser
	yamlData, err := yaml.Marshal(queries)
	if err != nil {
		return err
	}

	header := fmt.Sprintf("# Requêtes CRUD %s\n# Généré automatiquement depuis schema.yaml\n\n", tableName)
	queryFile := filepath.Join(queriesDir, tableName+".yaml")

	return os.WriteFile(queryFile, []byte(header+string(yamlData)), 0644)
}

// ReloadGateway demande à sogoctl de recharger sogoway.
func (sp *ServicePool) ReloadGateway() error {
	conn, err := net.DialTimeout("unix", "/run/sogoctl.sock", 2*time.Second)
	if err != nil {
		return fmt.Errorf("connect to sogoctl: %w", err)
	}
	defer conn.Close()

	// Envoyer la commande reload
	_, err = conn.Write([]byte("reload sogoway\n"))
	if err != nil {
		return fmt.Errorf("send command: %w", err)
	}

	// Lire la réponse
	buf := make([]byte, 256)
	conn.SetReadDeadline(time.Now().Add(2 * time.Second))
	n, err := conn.Read(buf)
	if err != nil {
		return fmt.Errorf("read response: %w", err)
	}

	response := string(buf[:n])
	if strings.HasPrefix(response, "error:") {
		return fmt.Errorf("%s", strings.TrimPrefix(response, "error: "))
	}

	return nil
}
