package main

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"strings"

	"sogoms.com/internal/admin"
	"sogoms.com/internal/auth"
	"sogoms.com/internal/config"
	"sogoms.com/internal/infra"
)

// AdminServer contient les dépendances des handlers.
type AdminServer struct {
	adminCfg    *admin.AdminConfig
	registry    *config.Registry
	sessions    *SessionStore
	version     string
	rateLimiter *RateLimiter
	perms       *admin.PermissionChecker
	audit       *admin.AuditLogger
	services    *ServicePool
	templates   *template.Template
	infraDB     *infra.DB
	sshPool     *infra.SSHPool
}

// getTemplates retourne les templates.
func (s *AdminServer) getTemplates() *template.Template {
	return s.templates
}

// HandleLoginPage affiche la page de login.
func (s *AdminServer) HandleLoginPage(w http.ResponseWriter, r *http.Request) {
	// Si déjà authentifié, rediriger vers dashboard
	if session, _ := s.sessions.GetSessionFromRequest(r); session != nil {
		http.Redirect(w, r, "/admin/", http.StatusSeeOther)
		return
	}

	// Générer un CSRF token pour le formulaire
	csrfToken, _ := generateSecureToken(32)

	data := map[string]any{
		"Title":     "Connexion",
		"CSRFToken": csrfToken,
		"Error":     r.URL.Query().Get("error"),
	}

	s.render(w, "login.html", data)
}

// HandleLogin traite la soumission du formulaire de login.
func (s *AdminServer) HandleLogin(w http.ResponseWriter, r *http.Request) {
	ip := getClientIP(r)
	userAgent := r.UserAgent()

	// Rate limiting
	if !s.rateLimiter.Allow(ip) {
		s.audit.LogLogin(false, "", ip, userAgent, "rate_limited")
		http.Redirect(w, r, "/admin/login?error=Trop+de+tentatives", http.StatusSeeOther)
		return
	}

	// Parser le formulaire
	if err := r.ParseForm(); err != nil {
		http.Redirect(w, r, "/admin/login?error=Formulaire+invalide", http.StatusSeeOther)
		return
	}

	username := r.FormValue("username")
	password := r.FormValue("password")

	// Enregistrer la tentative
	s.rateLimiter.Record(ip)

	// Vérifier les credentials
	user := s.adminCfg.GetUser(username)
	if user == nil {
		s.audit.LogLogin(false, username, ip, userAgent, "user_not_found")
		http.Redirect(w, r, "/admin/login?error=Identifiants+incorrects", http.StatusSeeOther)
		return
	}

	if !auth.VerifyPassword(user.PasswordHash, password) {
		s.audit.LogLogin(false, username, ip, userAgent, "wrong_password")
		http.Redirect(w, r, "/admin/login?error=Identifiants+incorrects", http.StatusSeeOther)
		return
	}

	// Vérifier si 2FA est requis
	needsTwoFA := user.NeedsTwoFA(&s.adminCfg.TwoFA)

	var session *Session
	var err error

	if needsTwoFA && user.TwoFAEnabled {
		// Créer une session en attente de validation 2FA
		session, err = s.sessions.CreatePending(username, user.Role, ip, userAgent)
		if err != nil {
			log.Printf("[admin] failed to create pending session: %v", err)
			http.Redirect(w, r, "/admin/login?error=Erreur+serveur", http.StatusSeeOther)
			return
		}

		// Définir le cookie
		s.sessions.SetCookie(w, session)

		// Log tentative
		s.audit.LogLogin(true, username, ip, userAgent, "pending_2fa")

		// Rediriger vers vérification 2FA
		http.Redirect(w, r, "/admin/2fa/verify", http.StatusSeeOther)
		return
	}

	// Pas de 2FA requis ou pas configuré - créer session normale
	session, err = s.sessions.Create(username, user.Role, ip, userAgent)
	if err != nil {
		log.Printf("[admin] failed to create session: %v", err)
		http.Redirect(w, r, "/admin/login?error=Erreur+serveur", http.StatusSeeOther)
		return
	}

	// Définir le cookie
	s.sessions.SetCookie(w, session)

	// Log succès
	s.audit.LogLogin(true, username, ip, userAgent, "")

	// Si 2FA requis mais pas encore configuré, rediriger vers setup
	if needsTwoFA && !user.TwoFAEnabled {
		http.Redirect(w, r, "/admin/2fa/setup?required=true", http.StatusSeeOther)
		return
	}

	// Rediriger vers dashboard
	http.Redirect(w, r, "/admin/", http.StatusSeeOther)
}

// HandleLogout déconnecte l'utilisateur.
func (s *AdminServer) HandleLogout(w http.ResponseWriter, r *http.Request) {
	session := GetSessionFromContext(r.Context())
	if session != nil {
		s.audit.LogLogout(session.Username, getClientIP(r))
		s.sessions.Delete(session.ID)
	}

	s.sessions.ClearCookie(w)
	http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
}

// HandleDashboard affiche le dashboard principal.
func (s *AdminServer) HandleDashboard(w http.ResponseWriter, r *http.Request) {
	session := GetSessionFromContext(r.Context())
	user := GetUserFromContext(r.Context())

	if session == nil || user == nil {
		http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
		return
	}

	// Récupérer les apps accessibles
	allApps := s.registry.Apps()
	accessibleApps := s.perms.GetAccessibleApps(user, allApps)

	data := map[string]any{
		"Title":       "Dashboard",
		"User":        user,
		"Session":     session,
		"CSRFToken":   session.CSRFToken,
		"IsSuperAdmin": user.IsSuperAdmin(),
		"Apps":        accessibleApps,
		"Permissions": s.perms.GetUserPermissions(user),
	}

	s.render(w, "dashboard.html", data)
}

// HandleAPIApps retourne la liste des apps (partial htmx).
func (s *AdminServer) HandleAPIApps(w http.ResponseWriter, r *http.Request) {
	user := GetUserFromContext(r.Context())
	if user == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	allApps := s.registry.Apps()
	accessibleApps := s.perms.GetAccessibleApps(user, allApps)

	// Construire les infos des apps
	type AppInfo struct {
		ID   string
		Name string
	}

	apps := make([]AppInfo, 0, len(accessibleApps))
	for _, appID := range accessibleApps {
		apps = append(apps, AppInfo{
			ID:   appID,
			Name: appID, // On pourrait charger le nom depuis la config
		})
	}

	data := map[string]any{
		"Apps": apps,
	}

	s.render(w, "partials/apps_list.html", data)
}

// HandleAPIServicesHealth retourne le statut des services (partial htmx).
func (s *AdminServer) HandleAPIServicesHealth(w http.ResponseWriter, r *http.Request) {
	user := GetUserFromContext(r.Context())
	if user == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	// Seul super_admin peut voir le statut des services
	if !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	statuses := s.services.HealthCheck()

	data := map[string]any{
		"Services": statuses,
	}

	s.render(w, "partials/services_status.html", data)
}

// AppInfo contient les informations d'une app pour le template.
type AppInfo struct {
	App              string
	Version          string
	BasePath         string
	Hosts            []string
	Database         DatabaseInfo
	Schema           bool
	SchemaTableCount int
	Queries          bool
	QueriesCount     int
	RoutesCount      int
}

// DatabaseInfo contient les infos de connexion DB.
type DatabaseInfo struct {
	Host string
	Port int
	User string
	Name string
}

// TableInfo contient les infos d'une table pour le template.
type TableInfo struct {
	Name        string
	ColumnCount int
	PrimaryKey  string
	SoftDelete  bool
	Cascade     bool
	ForeignKeys []string // Ex: ["project_id → projects", "user_id → users"]
}

// RouteInfo contient les infos d'une route pour le template.
type RouteInfo struct {
	Method  string
	Path    string
	Handler string
}

// HandleAppsPage affiche la liste des applications.
func (s *AdminServer) HandleAppsPage(w http.ResponseWriter, r *http.Request) {
	session := GetSessionFromContext(r.Context())
	user := GetUserFromContext(r.Context())

	if session == nil || user == nil {
		http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
		return
	}

	// Récupérer les apps accessibles
	allApps := s.registry.Apps()
	accessibleApps := s.perms.GetAccessibleApps(user, allApps)

	// Construire les infos détaillées
	apps := make([]AppInfo, 0, len(accessibleApps))
	for _, appID := range accessibleApps {
		cfg, ok := s.registry.GetByApp(appID)
		if !ok {
			continue
		}

		info := AppInfo{
			App:      cfg.App,
			Version:  cfg.Version,
			BasePath: cfg.BasePath,
			Hosts:    cfg.Hosts,
			Database: DatabaseInfo{
				Host: cfg.Database.Host,
				Port: cfg.Database.Port,
				User: cfg.Database.User,
				Name: cfg.Database.Name,
			},
		}

		if cfg.Schema != nil {
			info.Schema = true
			info.SchemaTableCount = len(cfg.Schema.Tables)
		}

		if cfg.Queries != nil {
			info.Queries = true
			info.QueriesCount = cfg.Queries.FileCount()
		}

		info.RoutesCount = len(cfg.Routes)

		apps = append(apps, info)
	}

	data := map[string]any{
		"Title":        "Applications",
		"User":         user,
		"Session":      session,
		"CSRFToken":    session.CSRFToken,
		"IsSuperAdmin": user.IsSuperAdmin(),
		"Apps":         apps,
	}

	s.render(w, "apps.html", data)
}

// HandleAppDetailPage affiche les détails d'une application.
func (s *AdminServer) HandleAppDetailPage(w http.ResponseWriter, r *http.Request) {
	session := GetSessionFromContext(r.Context())
	user := GetUserFromContext(r.Context())

	if session == nil || user == nil {
		http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
		return
	}

	// Récupérer l'app ID depuis l'URL
	appID := r.PathValue("appID")
	if appID == "" {
		http.Redirect(w, r, "/admin/apps", http.StatusSeeOther)
		return
	}

	// Vérifier que l'utilisateur a accès à cette app
	allApps := s.registry.Apps()
	accessibleApps := s.perms.GetAccessibleApps(user, allApps)
	hasAccess := false
	for _, a := range accessibleApps {
		if a == appID {
			hasAccess = true
			break
		}
	}
	if !hasAccess {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	// Récupérer la config de l'app
	cfg, ok := s.registry.GetByApp(appID)
	if !ok {
		http.Error(w, "App not found", http.StatusNotFound)
		return
	}

	// Construire les infos de l'app
	appInfo := AppInfo{
		App:      cfg.App,
		Version:  cfg.Version,
		BasePath: cfg.BasePath,
		Hosts:    cfg.Hosts,
		Database: DatabaseInfo{
			Host: cfg.Database.Host,
			Port: cfg.Database.Port,
			User: cfg.Database.User,
			Name: cfg.Database.Name,
		},
		RoutesCount: len(cfg.Routes),
	}

	if cfg.Schema != nil {
		appInfo.Schema = true
		appInfo.SchemaTableCount = len(cfg.Schema.Tables)
	}

	if cfg.Queries != nil {
		appInfo.Queries = true
		appInfo.QueriesCount = cfg.Queries.FileCount()
	}

	// Construire les infos des tables
	var tables []TableInfo
	if cfg.Schema != nil {
		for name, table := range cfg.Schema.Tables {
			// Clé primaire : composite ou simple
			pk := ""
			if len(table.Primary) > 1 {
				pk = strings.Join(table.Primary, ", ")
			} else {
				pk = table.GetPrimaryKey()
			}

			// Collecter les clés étrangères
			var fks []string
			for colName, col := range table.Columns {
				if col.Foreign != "" {
					// col.Foreign = "table.column", on extrait juste la table
					parts := strings.Split(col.Foreign, ".")
					refTable := parts[0]
					fks = append(fks, colName+" → "+refTable)
				}
			}

			tables = append(tables, TableInfo{
				Name:        name,
				ColumnCount: len(table.Columns),
				PrimaryKey:  pk,
				SoftDelete:  table.SoftDelete,
				Cascade:     table.Cascade,
				ForeignKeys: fks,
			})
		}
	}

	// Construire les infos des routes
	var routes []RouteInfo
	for _, route := range cfg.Routes {
		routes = append(routes, RouteInfo{
			Method:  route.Method,
			Path:    route.Path,
			Handler: route.Scenario,
		})
	}

	data := map[string]any{
		"Title":        cfg.App,
		"User":         user,
		"Session":      session,
		"CSRFToken":    session.CSRFToken,
		"IsSuperAdmin": user.IsSuperAdmin(),
		"App":          appInfo,
		"Tables":       tables,
		"Routes":       routes,
	}

	// Flash message depuis URL
	if flash := r.URL.Query().Get("flash"); flash != "" {
		data["FlashType"] = flash
		data["FlashMessage"] = r.URL.Query().Get("msg")
	}

	s.render(w, "app_detail.html", data)
}

// HandleAPICronJobs retourne la liste des jobs cron (partial htmx).
func (s *AdminServer) HandleAPICronJobs(w http.ResponseWriter, r *http.Request) {
	user := GetUserFromContext(r.Context())
	if user == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	// Seul super_admin peut voir les jobs cron
	if !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	jobs, err := s.services.GetCronJobs()
	if err != nil {
		log.Printf("[admin] cron jobs error: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	data := map[string]any{
		"Jobs": jobs,
	}

	s.render(w, "partials/cron_jobs.html", data)
}

// HandleAppNewPage affiche le formulaire de création d'app.
func (s *AdminServer) HandleAppNewPage(w http.ResponseWriter, r *http.Request) {
	session := GetSessionFromContext(r.Context())
	user := GetUserFromContext(r.Context())

	if session == nil || user == nil {
		http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
		return
	}

	// Seul super_admin peut créer des apps
	if !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	data := map[string]any{
		"Title":        "Nouvelle Application",
		"User":         user,
		"Session":      session,
		"CSRFToken":    session.CSRFToken,
		"IsSuperAdmin": user.IsSuperAdmin(),
	}

	s.render(w, "apps_new.html", data)
}

// HandleAppCreate traite la création d'une nouvelle app.
func (s *AdminServer) HandleAppCreate(w http.ResponseWriter, r *http.Request) {
	session := GetSessionFromContext(r.Context())
	user := GetUserFromContext(r.Context())

	if session == nil || user == nil {
		http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
		return
	}

	// Seul super_admin peut créer des apps
	if !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	if err := r.ParseForm(); err != nil {
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}

	// Récupérer les valeurs du formulaire
	appName := strings.TrimSpace(r.FormValue("app_name"))
	version := strings.TrimSpace(r.FormValue("version"))
	basePath := strings.TrimSpace(r.FormValue("base_path"))
	hostsRaw := strings.TrimSpace(r.FormValue("hosts"))
	dbHost := strings.TrimSpace(r.FormValue("db_host"))
	dbPort := strings.TrimSpace(r.FormValue("db_port"))
	dbUser := strings.TrimSpace(r.FormValue("db_user"))
	dbName := strings.TrimSpace(r.FormValue("db_name"))
	dbPassword := r.FormValue("db_password")
	jwtExpiry := strings.TrimSpace(r.FormValue("jwt_expiry"))

	// Validation basique
	if appName == "" || basePath == "" || hostsRaw == "" {
		http.Error(w, "Champs obligatoires manquants", http.StatusBadRequest)
		return
	}

	if dbHost == "" || dbPort == "" || dbUser == "" || dbName == "" || dbPassword == "" {
		http.Error(w, "Configuration base de données incomplète", http.StatusBadRequest)
		return
	}

	// Parser les hosts (un par ligne)
	var hosts []string
	for _, h := range strings.Split(hostsRaw, "\n") {
		h = strings.TrimSpace(h)
		if h != "" {
			hosts = append(hosts, h)
		}
	}

	if len(hosts) == 0 {
		http.Error(w, "Au moins un host requis", http.StatusBadRequest)
		return
	}

	// Créer l'app via le service
	err := s.services.CreateApp(appName, version, basePath, hosts, dbHost, dbPort, dbUser, dbName, dbPassword, jwtExpiry)
	if err != nil {
		log.Printf("[admin] create app error: %v", err)
		http.Error(w, "Erreur création app: "+err.Error(), http.StatusInternalServerError)
		return
	}

	// Recharger le registry local
	if err := s.registry.Load(); err != nil {
		log.Printf("[admin] reload registry error: %v", err)
	}

	// Recharger sogoway pour qu'il connaisse la nouvelle app
	if err := s.services.ReloadGateway(); err != nil {
		log.Printf("[admin] reload gateway error: %v", err)
	}

	// Log l'action
	s.audit.LogAction(user.Username, "create_app", appName, map[string]any{
		"ip": getClientIP(r),
	})

	// Rediriger vers la page de l'app
	http.Redirect(w, r, "/admin/apps/"+appName, http.StatusSeeOther)
}

// HandleAppScanDB scanne la base de données et génère le schema.yaml.
func (s *AdminServer) HandleAppScanDB(w http.ResponseWriter, r *http.Request) {
	session := GetSessionFromContext(r.Context())
	user := GetUserFromContext(r.Context())

	if session == nil || user == nil {
		http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
		return
	}

	// Seul super_admin peut scanner les bases
	if !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	appID := r.PathValue("appID")
	if appID == "" {
		http.Error(w, "App ID required", http.StatusBadRequest)
		return
	}

	// Vérifier que l'app existe
	if _, ok := s.registry.GetByApp(appID); !ok {
		http.Error(w, "App not found", http.StatusNotFound)
		return
	}

	// Scanner la base et générer le schema
	tableCount, err := s.services.ScanAndGenerateSchema(appID)
	if err != nil {
		log.Printf("[admin] scan db error: %v", err)
		redirectURL := fmt.Sprintf("/admin/apps/%s?flash=error&msg=%s", appID, url.QueryEscape("Erreur scan: "+err.Error()))
		http.Redirect(w, r, redirectURL, http.StatusSeeOther)
		return
	}

	// Générer les fichiers queries depuis le schema
	if err := GenerateQueriesFromSchema(appID); err != nil {
		log.Printf("[admin] generate queries error: %v", err)
	} else {
		log.Printf("[admin] queries generated for app: %s", appID)
	}

	// Mettre à jour login_data dans auth.yaml
	if err := UpdateLoginData(appID); err != nil {
		log.Printf("[admin] update login_data error: %v", err)
	} else {
		log.Printf("[admin] login_data updated for app: %s", appID)
	}

	// Générer les routes dans app.yaml
	if err := GenerateRoutesFromSchema(appID); err != nil {
		log.Printf("[admin] generate routes error: %v", err)
	} else {
		log.Printf("[admin] routes generated for app: %s", appID)
	}

	// Recharger le registry local
	if err := s.registry.Load(); err != nil {
		log.Printf("[admin] reload registry error: %v", err)
	}

	// Demander à sogoway de recharger sa config
	if err := s.services.ReloadGateway(); err != nil {
		log.Printf("[admin] reload gateway error: %v", err)
		// On ne bloque pas, on continue avec le message de succès
	} else {
		log.Printf("[admin] gateway reloaded successfully")
	}

	// Log l'action
	s.audit.LogAction(user.Username, "scan_db", appID, map[string]any{
		"ip":     getClientIP(r),
		"tables": tableCount,
	})

	// Rediriger vers la page de l'app avec message de succès
	msg := fmt.Sprintf("Scan terminé : %d tables détectées", tableCount)
	redirectURL := fmt.Sprintf("/admin/apps/%s?flash=success&msg=%s", appID, url.QueryEscape(msg))
	http.Redirect(w, r, redirectURL, http.StatusSeeOther)
}

// render rend un template.
func (s *AdminServer) render(w http.ResponseWriter, name string, data map[string]any) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")

	// Ajouter la version à toutes les pages
	data["SogVersion"] = s.version

	tmpl := s.getTemplates()
	if err := tmpl.ExecuteTemplate(w, name, data); err != nil {
		log.Printf("[admin] template error: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
	}
}
