// Package config gère le chargement des configurations YAML.
package config

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"gopkg.in/yaml.v3"
)

// AppConfig représente la configuration d'une application cliente.
type AppConfig struct {
	App      string   `yaml:"app"`
	Version  string   `yaml:"version"`
	BasePath string   `yaml:"base_path"`
	Hosts    []string `yaml:"hosts"`
	Database Database `yaml:"database"`
	Auth     Auth     `yaml:"auth"`
	Routes   []Route  `yaml:"routes"`
	Queries  *Queries // Chargé depuis config/apps/{app}/queries/
	Schema   *Schema  // Chargé depuis config/apps/{app}/schema.yaml
}

// Queries stocke les requêtes SQL par domaine.
type Queries struct {
	Auth     map[string]any `yaml:",inline"`
	Projects map[string]any `yaml:",inline"`
	Tasks    map[string]any `yaml:",inline"`
	Tags     map[string]any `yaml:",inline"`
	Statuses map[string]any `yaml:",inline"`
	files    map[string]map[string]any // domaine -> clé -> requête
}

// Get retourne une requête par domaine et clé.
func (q *Queries) Get(domain, key string) string {
	if q == nil || q.files == nil {
		return ""
	}
	if domainMap, ok := q.files[domain]; ok {
		if val, ok := domainMap[key]; ok {
			if s, ok := val.(string); ok {
				return strings.TrimSpace(s)
			}
		}
	}
	return ""
}

// FileCount retourne le nombre de fichiers de queries chargés.
func (q *Queries) FileCount() int {
	if q == nil || q.files == nil {
		return 0
	}
	return len(q.files)
}

// GetMap retourne une map de requêtes (ex: login_data).
func (q *Queries) GetMap(domain, key string) map[string]string {
	if q == nil || q.files == nil {
		return nil
	}
	if domainMap, ok := q.files[domain]; ok {
		if val, ok := domainMap[key]; ok {
			if m, ok := val.(map[string]any); ok {
				result := make(map[string]string)
				for k, v := range m {
					if s, ok := v.(string); ok {
						result[k] = strings.TrimSpace(s)
					}
				}
				return result
			}
		}
	}
	return nil
}

// QueryConfig représente une requête paramétrable.
type QueryConfig struct {
	Query   string
	Filters map[string]string
	Order   string
}

// GetQuery retourne une QueryConfig pour un domaine et une clé.
func (q *Queries) GetQuery(domain, key string) *QueryConfig {
	if q == nil || q.files == nil {
		return nil
	}
	domainMap, ok := q.files[domain]
	if !ok {
		return nil
	}
	val, ok := domainMap[key]
	if !ok {
		return nil
	}

	m, ok := val.(map[string]any)
	if !ok {
		// Ancienne syntaxe : juste une string
		if s, ok := val.(string); ok {
			return &QueryConfig{Query: strings.TrimSpace(s)}
		}
		return nil
	}

	qc := &QueryConfig{
		Filters: make(map[string]string),
	}

	if query, ok := m["query"].(string); ok {
		qc.Query = strings.TrimSpace(query)
	}
	if order, ok := m["order"].(string); ok {
		qc.Order = strings.TrimSpace(order)
	}
	if filters, ok := m["filters"].(map[string]any); ok {
		for k, v := range filters {
			if s, ok := v.(string); ok {
				qc.Filters[k] = strings.TrimSpace(s)
			}
		}
	}

	return qc
}

// CUDConfig représente une config pour Create/Update/Delete.
type CUDConfig struct {
	Table   string
	Fields  []string
	Filters map[string]string
}

// GetCUD retourne une CUDConfig pour un domaine et une clé (create/update/delete).
func (q *Queries) GetCUD(domain, key string) *CUDConfig {
	if q == nil || q.files == nil {
		return nil
	}
	domainMap, ok := q.files[domain]
	if !ok {
		return nil
	}
	val, ok := domainMap[key]
	if !ok {
		return nil
	}

	m, ok := val.(map[string]any)
	if !ok {
		return nil
	}

	cud := &CUDConfig{
		Filters: make(map[string]string),
	}

	if table, ok := m["table"].(string); ok {
		cud.Table = table
	}
	if fields, ok := m["fields"].([]any); ok {
		for _, f := range fields {
			if s, ok := f.(string); ok {
				cud.Fields = append(cud.Fields, s)
			}
		}
	}
	if filters, ok := m["filters"].(map[string]any); ok {
		for k, v := range filters {
			if s, ok := v.(string); ok {
				cud.Filters[k] = strings.TrimSpace(s)
			}
		}
	}

	return cud
}

// GetFilter retourne le filtre pour un rôle donné.
func (cud *CUDConfig) GetFilter(role string) string {
	if cud == nil {
		return ""
	}
	if f, ok := cud.Filters[role]; ok {
		return f
	}
	if f, ok := cud.Filters["default"]; ok {
		return f
	}
	return ""
}

// Build construit la requête SQL finale avec filtres et ordre.
// role: rôle de l'utilisateur (ou "default")
// params: map de placeholders à remplacer (:user_id, :id, etc.)
func (qc *QueryConfig) Build(role string, params map[string]any) (string, []any) {
	if qc == nil {
		return "", nil
	}

	query := qc.Query

	// Déterminer le filtre à appliquer
	filter := ""
	if f, ok := qc.Filters[role]; ok {
		filter = f
	} else if f, ok := qc.Filters["default"]; ok {
		filter = f
	}

	// Construire la requête
	hasWhere := strings.Contains(strings.ToUpper(query), " WHERE ")

	if filter != "" {
		if hasWhere {
			query += " AND " + filter
		} else {
			query += " WHERE " + filter
		}
	}

	if qc.Order != "" {
		query += " ORDER BY " + qc.Order
	}

	// Remplacer les placeholders :name par ? et collecter les args
	var args []any
	for {
		idx := strings.Index(query, ":")
		if idx == -1 {
			break
		}
		// Trouver la fin du placeholder
		end := idx + 1
		for end < len(query) && (query[end] == '_' || (query[end] >= 'a' && query[end] <= 'z') || (query[end] >= 'A' && query[end] <= 'Z') || (query[end] >= '0' && query[end] <= '9')) {
			end++
		}
		placeholder := query[idx+1 : end]
		if val, ok := params[placeholder]; ok {
			args = append(args, val)
			query = query[:idx] + "?" + query[end:]
		} else {
			// Placeholder non trouvé, on laisse tel quel (peut être une erreur)
			break
		}
	}

	return query, args
}

// Auth contient la configuration d'authentification.
type Auth struct {
	JWTSecretFile string `yaml:"jwt_secret_file"`
	JWTExpiry     string `yaml:"jwt_expiry"`
	jwtSecret     string // Chargé depuis le fichier
}

// JWTSecret retourne le secret JWT (chargé depuis le fichier).
func (a *Auth) JWTSecret() string {
	return a.jwtSecret
}

// Database contient la configuration de connexion à la base de données.
type Database struct {
	Host         string `yaml:"host"`
	Port         int    `yaml:"port"`
	User         string `yaml:"user"`
	PasswordFile string `yaml:"password_file"`
	Name         string `yaml:"name"`
	password     string // Chargé depuis le fichier
}

// Password retourne le mot de passe (chargé depuis le fichier).
func (d *Database) Password() string {
	return d.password
}

// DSN retourne la chaîne de connexion MySQL/MariaDB.
func (d *Database) DSN() string {
	return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&charset=utf8mb4",
		d.User, d.password, d.Host, d.Port, d.Name)
}

// Route représente une route API.
type Route struct {
	Path     string `yaml:"path"`
	Method   string `yaml:"method"`
	Scenario string `yaml:"scenario"`
	Auth     *bool  `yaml:"auth,omitempty"`
}

// Registry stocke les configurations des applications.
type Registry struct {
	configDir string
	apps      map[string]*AppConfig // Par app_id
	byHost    map[string]*AppConfig // Par hostname
	mu        sync.RWMutex
}

// NewRegistry crée un nouveau registre de configurations.
func NewRegistry(configDir string) *Registry {
	return &Registry{
		configDir: configDir,
		apps:      make(map[string]*AppConfig),
		byHost:    make(map[string]*AppConfig),
	}
}

// Load charge toutes les configurations depuis le répertoire apps.
func (r *Registry) Load() error {
	r.mu.Lock()
	defer r.mu.Unlock()

	appsDir := filepath.Join(r.configDir, "apps")
	entries, err := os.ReadDir(appsDir)
	if err != nil {
		return fmt.Errorf("read apps dir: %w", err)
	}

	for _, entry := range entries {
		if !entry.IsDir() {
			continue
		}

		appID := entry.Name()
		appConfigPath := filepath.Join(appsDir, appID, "app.yaml")

		// Vérifier que app.yaml existe
		if _, err := os.Stat(appConfigPath); os.IsNotExist(err) {
			continue
		}

		cfg, err := r.loadAppConfig(appConfigPath, appID)
		if err != nil {
			return fmt.Errorf("load %s: %w", appID, err)
		}

		r.apps[cfg.App] = cfg
		for _, host := range cfg.Hosts {
			r.byHost[host] = cfg
		}
	}

	return nil
}

// loadAppConfig charge une configuration d'application depuis un fichier YAML.
func (r *Registry) loadAppConfig(path string, appID string) (*AppConfig, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}

	var cfg AppConfig
	if err := yaml.Unmarshal(data, &cfg); err != nil {
		return nil, err
	}

	// Charger le mot de passe DB depuis le fichier
	if cfg.Database.PasswordFile != "" {
		passData, err := os.ReadFile(cfg.Database.PasswordFile)
		if err != nil {
			return nil, fmt.Errorf("read db password file: %w", err)
		}
		cfg.Database.password = strings.TrimSpace(string(passData))
	}

	// Charger le secret JWT depuis le fichier
	if cfg.Auth.JWTSecretFile != "" {
		secretData, err := os.ReadFile(cfg.Auth.JWTSecretFile)
		if err != nil {
			return nil, fmt.Errorf("read jwt secret file: %w", err)
		}
		cfg.Auth.jwtSecret = strings.TrimSpace(string(secretData))
	}

	// Port DB par défaut
	if cfg.Database.Port == 0 {
		cfg.Database.Port = 3306
	}

	// Expiry JWT par défaut
	if cfg.Auth.JWTExpiry == "" {
		cfg.Auth.JWTExpiry = "24h"
	}

	// Charger les requêtes depuis config/apps/{app}/queries/
	cfg.Queries = r.loadQueries(appID)

	// Charger le schema depuis config/apps/{app}/schema.yaml
	cfg.Schema = loadSchema(r.configDir, appID)

	return &cfg, nil
}

// loadQueries charge les fichiers de requêtes pour une application.
func (r *Registry) loadQueries(appID string) *Queries {
	queriesDir := filepath.Join(r.configDir, "apps", appID, "queries")
	entries, err := os.ReadDir(queriesDir)
	if err != nil {
		return nil // Pas de répertoire queries, c'est OK
	}

	q := &Queries{
		files: make(map[string]map[string]any),
	}

	for _, entry := range entries {
		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".yaml") {
			continue
		}

		domain := strings.TrimSuffix(entry.Name(), ".yaml")
		path := filepath.Join(queriesDir, entry.Name())

		data, err := os.ReadFile(path)
		if err != nil {
			continue
		}

		var content map[string]any
		if err := yaml.Unmarshal(data, &content); err != nil {
			continue
		}

		q.files[domain] = content
	}

	return q
}

// GetByApp retourne la configuration d'une application par son ID.
func (r *Registry) GetByApp(appID string) (*AppConfig, bool) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	cfg, ok := r.apps[appID]
	return cfg, ok
}

// GetByHost retourne la configuration d'une application par son hostname.
func (r *Registry) GetByHost(host string) (*AppConfig, bool) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	cfg, ok := r.byHost[host]
	return cfg, ok
}

// Apps retourne la liste des IDs d'applications chargées.
func (r *Registry) Apps() []string {
	r.mu.RLock()
	defer r.mu.RUnlock()
	apps := make([]string, 0, len(r.apps))
	for app := range r.apps {
		apps = append(apps, app)
	}
	return apps
}
