// Package config - Schema représente la structure de la base de données.
package config

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

	"gopkg.in/yaml.v3"
)

// Schema représente le schema d'une application.
type Schema struct {
	App     string           `yaml:"app"`
	Version string           `yaml:"version"`
	Tables  map[string]*Table `yaml:"tables"`
}

// Table représente une table de la base de données.
type Table struct {
	Columns    map[string]*Column `yaml:"columns"`
	Primary    []string           `yaml:"primary,omitempty"`     // Clé primaire composite
	CRUD       []string           `yaml:"crud"`
	Order      string             `yaml:"order,omitempty"`
	SoftDelete bool               `yaml:"soft_delete,omitempty"` // Si true, DELETE → UPDATE deleted_at
	Cascade    bool               `yaml:"cascade,omitempty"`     // Si true, soft delete en cascade sur enfants
}

// Column représente une colonne d'une table.
type Column struct {
	Type     string `yaml:"type"`
	Length   int64  `yaml:"length,omitempty"`
	Required bool   `yaml:"required,omitempty"`
	Primary  bool   `yaml:"primary,omitempty"`
	Auto     bool   `yaml:"auto,omitempty"`
	Unique   bool   `yaml:"unique,omitempty"`
	Default  string `yaml:"default,omitempty"`
	Foreign  string `yaml:"foreign,omitempty"` // table.column
	Filter   string `yaml:"filter,omitempty"`  // "owner" pour filtrage auto
}

// HasCRUD vérifie si une opération CRUD est autorisée pour cette table.
func (t *Table) HasCRUD(op string) bool {
	for _, c := range t.CRUD {
		if c == op {
			return true
		}
	}
	return false
}

// GetOwnerColumn retourne le nom de la colonne avec filter: owner, ou "".
func (t *Table) GetOwnerColumn() string {
	for name, col := range t.Columns {
		if col.Filter == "owner" {
			return name
		}
	}
	return ""
}

// GetPrimaryKey retourne le nom de la clé primaire (simple).
func (t *Table) GetPrimaryKey() string {
	// D'abord chercher dans Primary (composite)
	if len(t.Primary) == 1 {
		return t.Primary[0]
	}
	// Sinon chercher une colonne avec primary: true
	for name, col := range t.Columns {
		if col.Primary {
			return name
		}
	}
	return "id" // Par défaut
}

// IsCompositePK vérifie si la table a une clé primaire composite.
func (t *Table) IsCompositePK() bool {
	return len(t.Primary) > 1
}

// IsSoftDelete vérifie si la table utilise le soft delete.
func (t *Table) IsSoftDelete() bool {
	return t.SoftDelete
}

// IsCascade vérifie si le cascade delete est activé.
func (t *Table) IsCascade() bool {
	return t.Cascade
}

// ChildRelation représente une relation enfant (FK vers table parent).
type ChildRelation struct {
	ChildTable  string // Nom de la table enfant
	ChildColumn string // Colonne FK dans la table enfant
	ParentTable string // Nom de la table parent
}

// GetChildRelations retourne les tables enfants qui ont une FK vers parentTable.
func (s *Schema) GetChildRelations(parentTable string) []ChildRelation {
	var children []ChildRelation

	for tableName, table := range s.Tables {
		if tableName == parentTable {
			continue // Skip la table elle-même
		}

		for colName, col := range table.Columns {
			if col.Foreign != "" {
				// col.Foreign = "table.column"
				parts := strings.Split(col.Foreign, ".")
				if len(parts) >= 1 && parts[0] == parentTable {
					children = append(children, ChildRelation{
						ChildTable:  tableName,
						ChildColumn: colName,
						ParentTable: parentTable,
					})
				}
			}
		}
	}

	return children
}

// GetSelectColumns retourne la liste des colonnes pour un SELECT.
func (t *Table) GetSelectColumns() []string {
	cols := make([]string, 0, len(t.Columns))
	for name := range t.Columns {
		cols = append(cols, name)
	}
	return cols
}

// GetInsertColumns retourne les colonnes pour un INSERT (exclut auto-increment).
func (t *Table) GetInsertColumns() []string {
	cols := make([]string, 0, len(t.Columns))
	for name, col := range t.Columns {
		if !col.Auto {
			cols = append(cols, name)
		}
	}
	return cols
}

// GetUpdateColumns retourne les colonnes pour un UPDATE (exclut PK et auto).
func (t *Table) GetUpdateColumns() []string {
	cols := make([]string, 0, len(t.Columns))
	pk := t.GetPrimaryKey()
	for name, col := range t.Columns {
		if name != pk && !col.Auto && col.Filter != "owner" {
			cols = append(cols, name)
		}
	}
	return cols
}

// BuildListQuery génère la requête SELECT pour list.
// Filtre automatiquement les enregistrements supprimés si soft_delete est activé.
func (t *Table) BuildListQuery(tableName string) string {
	cols := t.GetSelectColumns()
	query := fmt.Sprintf("SELECT %s FROM %s", strings.Join(cols, ", "), tableName)

	// Construire les conditions WHERE
	var conditions []string

	// Filtre owner si présent
	if ownerCol := t.GetOwnerColumn(); ownerCol != "" {
		conditions = append(conditions, fmt.Sprintf("%s = ?", ownerCol))
	}

	// Filtre soft delete
	if t.SoftDelete {
		conditions = append(conditions, "deleted_at IS NULL")
	}

	if len(conditions) > 0 {
		query += " WHERE " + strings.Join(conditions, " AND ")
	}

	// Ajouter ORDER BY si défini
	if t.Order != "" {
		query += " ORDER BY " + t.Order
	}

	return query
}

// BuildShowQuery génère la requête SELECT pour show (un seul enregistrement).
// Filtre automatiquement les enregistrements supprimés si soft_delete est activé.
func (t *Table) BuildShowQuery(tableName string) string {
	cols := t.GetSelectColumns()
	pk := t.GetPrimaryKey()
	query := fmt.Sprintf("SELECT %s FROM %s WHERE %s = ?", strings.Join(cols, ", "), tableName, pk)

	// Ajouter filtre owner si présent
	if ownerCol := t.GetOwnerColumn(); ownerCol != "" {
		query += fmt.Sprintf(" AND %s = ?", ownerCol)
	}

	// Filtre soft delete
	if t.SoftDelete {
		query += " AND deleted_at IS NULL"
	}

	return query
}

// BuildInsertQuery génère la requête INSERT.
func (t *Table) BuildInsertQuery(tableName string, data map[string]any) (string, []any) {
	cols := make([]string, 0)
	placeholders := make([]string, 0)
	args := make([]any, 0)

	for name, col := range t.Columns {
		if col.Auto {
			continue // Skip auto-increment
		}

		if val, ok := data[name]; ok {
			cols = append(cols, name)
			placeholders = append(placeholders, "?")
			args = append(args, val)
		} else if col.Required && col.Default == "" {
			// Champ requis sans valeur et sans default - sera une erreur DB
			continue
		}
	}

	query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)",
		tableName,
		strings.Join(cols, ", "),
		strings.Join(placeholders, ", "))

	return query, args
}

// BuildUpdateQuery génère la requête UPDATE.
func (t *Table) BuildUpdateQuery(tableName string, id any, ownerID any, data map[string]any) (string, []any) {
	setClauses := make([]string, 0)
	args := make([]any, 0)

	pk := t.GetPrimaryKey()
	ownerCol := t.GetOwnerColumn()

	for name := range t.Columns {
		// Skip PK, auto, et owner
		if name == pk || t.Columns[name].Auto || name == ownerCol {
			continue
		}

		if val, ok := data[name]; ok {
			setClauses = append(setClauses, name+" = ?")
			args = append(args, val)
		}
	}

	if len(setClauses) == 0 {
		return "", nil
	}

	query := fmt.Sprintf("UPDATE %s SET %s WHERE %s = ?",
		tableName,
		strings.Join(setClauses, ", "),
		pk)
	args = append(args, id)

	// Ajouter filtre owner
	if ownerCol != "" && ownerID != nil {
		query += fmt.Sprintf(" AND %s = ?", ownerCol)
		args = append(args, ownerID)
	}

	return query, args
}

// BuildDeleteQuery génère la requête DELETE.
func (t *Table) BuildDeleteQuery(tableName string, id any, ownerID any) (string, []any) {
	pk := t.GetPrimaryKey()
	query := fmt.Sprintf("DELETE FROM %s WHERE %s = ?", tableName, pk)
	args := []any{id}

	// Ajouter filtre owner
	if ownerCol := t.GetOwnerColumn(); ownerCol != "" && ownerID != nil {
		query += fmt.Sprintf(" AND %s = ?", ownerCol)
		args = append(args, ownerID)
	}

	return query, args
}

// ValidateInput valide les données d'entrée selon le schema.
// Retourne les données filtrées (seuls les champs connus sont gardés).
func (t *Table) ValidateInput(data map[string]any) map[string]any {
	filtered := make(map[string]any)
	for name := range t.Columns {
		if val, ok := data[name]; ok {
			filtered[name] = val
		}
	}
	return filtered
}

// loadSchema charge le schema depuis config/apps/{app}/schema.yaml.
func loadSchema(configDir, appID string) *Schema {
	schemaPath := filepath.Join(configDir, "apps", appID, "schema.yaml")

	data, err := os.ReadFile(schemaPath)
	if err != nil {
		return nil // Pas de schema, c'est OK
	}

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

	return &schema
}
