package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"time"

	"sogoms.com/internal/infra"
)

// ============================================================================
// Servers
// ============================================================================

// HandleInfraPage affiche la page principale de l'infrastructure.
func (s *AdminServer) HandleInfraPage(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 gérer l'infra
	if !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	// Récupérer les serveurs
	servers, err := s.infraDB.ListServers()
	if err != nil {
		log.Printf("[admin] list servers error: %v", err)
		servers = []infra.Server{}
	}

	// Pour chaque serveur, récupérer les containers
	type ServerView struct {
		infra.Server
		Containers     []infra.Container
		ContainerCount int
		NginxCount     int
	}

	serverViews := make([]ServerView, 0, len(servers))
	for _, srv := range servers {
		containers, _ := s.infraDB.ListContainersByServer(srv.ID)
		nginxConfigs, _ := s.infraDB.ListNginxConfigsByServer(srv.ID)

		serverViews = append(serverViews, ServerView{
			Server:         srv,
			Containers:     containers,
			ContainerCount: len(containers),
			NginxCount:     len(nginxConfigs),
		})
	}

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

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

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

// HandleServerNewPage affiche le formulaire d'ajout de serveur.
func (s *AdminServer) HandleServerNewPage(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
	}

	if !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	data := map[string]any{
		"Title":        "Nouveau Serveur",
		"User":         user,
		"Session":      session,
		"CSRFToken":    session.CSRFToken,
		"IsSuperAdmin": true,
	}

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

// HandleServerCreate crée un nouveau serveur.
func (s *AdminServer) HandleServerCreate(w http.ResponseWriter, r *http.Request) {
	session := GetSessionFromContext(r.Context())
	user := GetUserFromContext(r.Context())

	if session == nil || user == nil || !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

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

	// Parser les valeurs
	sshPort, _ := strconv.Atoi(r.FormValue("ssh_port"))
	if sshPort == 0 {
		sshPort = 22
	}

	server := &infra.Server{
		Name:       r.FormValue("name"),
		Host:       r.FormValue("host"),
		VpnIP:      r.FormValue("vpn_ip"),
		SSHPort:    sshPort,
		SSHUser:    r.FormValue("ssh_user"),
		SSHKeyFile: r.FormValue("ssh_key_file"),
		HasIncus:   r.FormValue("has_incus") == "on",
		HasNginx:   r.FormValue("has_nginx") == "on",
		Status:     infra.ServerStatusUnknown,
	}

	if server.Name == "" || server.Host == "" {
		http.Error(w, "Nom et host requis", http.StatusBadRequest)
		return
	}

	if err := s.infraDB.CreateServer(server); err != nil {
		log.Printf("[admin] create server error: %v", err)
		http.Error(w, "Erreur création serveur", http.StatusInternalServerError)
		return
	}

	s.audit.LogAction(user.Username, "create_server", server.Name, map[string]any{
		"ip":   getClientIP(r),
		"host": server.Host,
	})

	http.Redirect(w, r, "/admin/infra?flash=success&msg=Serveur+créé", http.StatusSeeOther)
}

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

	if session == nil || user == nil || !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	serverID, err := strconv.ParseInt(r.PathValue("serverID"), 10, 64)
	if err != nil {
		http.Error(w, "Invalid server ID", http.StatusBadRequest)
		return
	}

	server, err := s.infraDB.GetServer(serverID)
	if err != nil || server == nil {
		http.Error(w, "Server not found", http.StatusNotFound)
		return
	}

	// Récupérer les containers et nginx configs
	containers, _ := s.infraDB.ListContainersByServer(serverID)
	nginxConfigs, _ := s.infraDB.ListNginxConfigsByServer(serverID)

	data := map[string]any{
		"Title":        server.Name,
		"User":         user,
		"Session":      session,
		"CSRFToken":    session.CSRFToken,
		"IsSuperAdmin": true,
		"Server":       server,
		"Containers":   containers,
		"NginxConfigs": nginxConfigs,
	}

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

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

// HandleServerDelete supprime un serveur.
func (s *AdminServer) HandleServerDelete(w http.ResponseWriter, r *http.Request) {
	session := GetSessionFromContext(r.Context())
	user := GetUserFromContext(r.Context())

	if session == nil || user == nil || !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	serverID, err := strconv.ParseInt(r.PathValue("serverID"), 10, 64)
	if err != nil {
		http.Error(w, "Invalid server ID", http.StatusBadRequest)
		return
	}

	server, _ := s.infraDB.GetServer(serverID)
	if server == nil {
		http.Error(w, "Server not found", http.StatusNotFound)
		return
	}

	// Déconnecter SSH si connecté
	s.sshPool.Disconnect(serverID)

	if err := s.infraDB.DeleteServer(serverID); err != nil {
		log.Printf("[admin] delete server error: %v", err)
		http.Error(w, "Erreur suppression", http.StatusInternalServerError)
		return
	}

	s.audit.LogAction(user.Username, "delete_server", server.Name, nil)

	http.Redirect(w, r, "/admin/infra?flash=success&msg=Serveur+supprimé", http.StatusSeeOther)
}

// HandleServerTestSSH teste la connexion SSH à un serveur.
func (s *AdminServer) HandleServerTestSSH(w http.ResponseWriter, r *http.Request) {
	session := GetSessionFromContext(r.Context())
	user := GetUserFromContext(r.Context())

	if session == nil || user == nil || !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	serverID, err := strconv.ParseInt(r.PathValue("serverID"), 10, 64)
	if err != nil {
		http.Error(w, "Invalid server ID", http.StatusBadRequest)
		return
	}

	server, err := s.infraDB.GetServer(serverID)
	if err != nil || server == nil {
		http.Error(w, "Server not found", http.StatusNotFound)
		return
	}

	// Tester la connexion SSH
	client, err := s.sshPool.Connect(server)
	if err != nil {
		s.infraDB.UpdateServerStatus(serverID, infra.ServerStatusOffline)
		msg := fmt.Sprintf("Erreur SSH: %v", err)
		http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=error&msg=%s", serverID, msg), http.StatusSeeOther)
		return
	}

	// Exécuter une commande de test
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	result, err := client.ExecSimple(ctx, "hostname && uptime")
	if err != nil {
		s.infraDB.UpdateServerStatus(serverID, infra.ServerStatusOffline)
		msg := fmt.Sprintf("Erreur commande: %v", err)
		http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=error&msg=%s", serverID, msg), http.StatusSeeOther)
		return
	}

	// Mise à jour du statut
	s.infraDB.UpdateServerStatus(serverID, infra.ServerStatusOnline)

	s.audit.LogAction(user.Username, "test_ssh", server.Name, map[string]any{
		"result": result,
	})

	msg := fmt.Sprintf("Connexion OK: %s", result)
	http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=success&msg=%s", serverID, msg), http.StatusSeeOther)
}

// HandleServerSyncContainers synchronise les containers depuis Incus.
func (s *AdminServer) HandleServerSyncContainers(w http.ResponseWriter, r *http.Request) {
	session := GetSessionFromContext(r.Context())
	user := GetUserFromContext(r.Context())

	if session == nil || user == nil || !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	serverID, err := strconv.ParseInt(r.PathValue("serverID"), 10, 64)
	if err != nil {
		http.Error(w, "Invalid server ID", http.StatusBadRequest)
		return
	}

	server, err := s.infraDB.GetServer(serverID)
	if err != nil || server == nil {
		http.Error(w, "Server not found", http.StatusNotFound)
		return
	}

	if !server.HasIncus {
		http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=error&msg=Incus+non+activé", serverID), http.StatusSeeOther)
		return
	}

	// Connexion SSH
	client, err := s.sshPool.Connect(server)
	if err != nil {
		http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=error&msg=Erreur+SSH", serverID), http.StatusSeeOther)
		return
	}

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

	// Récupérer les containers Incus
	incusContainers, err := client.ListIncusContainers(ctx)
	if err != nil {
		msg := fmt.Sprintf("Erreur Incus: %v", err)
		http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=error&msg=%s", serverID, msg), http.StatusSeeOther)
		return
	}

	// Synchroniser avec la base
	synced := 0
	for _, ic := range incusContainers {
		// Vérifier si existe déjà
		existing, _ := s.infraDB.ListContainersByServer(serverID)
		found := false
		for _, c := range existing {
			if c.IncusName == ic.Name {
				// Mettre à jour le statut
				status := infra.ContainerStatusUnknown
				if ic.State == "running" {
					status = infra.ContainerStatusRunning
				} else if ic.State == "stopped" {
					status = infra.ContainerStatusStopped
				}
				s.infraDB.UpdateContainerStatus(c.ID, status)
				found = true
				break
			}
		}

		if !found {
			// Créer le container
			ip := ""
			if len(ic.IPv4) > 0 {
				ip = ic.IPv4[0]
			}

			status := infra.ContainerStatusUnknown
			if ic.State == "running" {
				status = infra.ContainerStatusRunning
			} else if ic.State == "stopped" {
				status = infra.ContainerStatusStopped
			}

			container := &infra.Container{
				ServerID:  serverID,
				Name:      ic.Name,
				IncusName: ic.Name,
				IP:        ip,
				Image:     ic.Image,
				Status:    status,
			}
			if err := s.infraDB.CreateContainer(container); err == nil {
				synced++
			}
		}
	}

	s.audit.LogAction(user.Username, "sync_containers", server.Name, map[string]any{
		"synced": synced,
		"total":  len(incusContainers),
	})

	msg := fmt.Sprintf("Sync OK: %d nouveaux containers", synced)
	http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=success&msg=%s", serverID, msg), http.StatusSeeOther)
}

// ============================================================================
// Containers
// ============================================================================

// HandleContainerAction effectue une action sur un container (start/stop/restart).
func (s *AdminServer) HandleContainerAction(w http.ResponseWriter, r *http.Request) {
	session := GetSessionFromContext(r.Context())
	user := GetUserFromContext(r.Context())

	if session == nil || user == nil || !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	containerID, err := strconv.ParseInt(r.PathValue("containerID"), 10, 64)
	if err != nil {
		http.Error(w, "Invalid container ID", http.StatusBadRequest)
		return
	}

	action := r.FormValue("action")
	if action != "start" && action != "stop" && action != "restart" {
		http.Error(w, "Invalid action", http.StatusBadRequest)
		return
	}

	container, err := s.infraDB.GetContainer(containerID)
	if err != nil || container == nil {
		http.Error(w, "Container not found", http.StatusNotFound)
		return
	}

	server, err := s.infraDB.GetServer(container.ServerID)
	if err != nil || server == nil {
		http.Error(w, "Server not found", http.StatusNotFound)
		return
	}

	// Connexion SSH
	client, err := s.sshPool.Connect(server)
	if err != nil {
		http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=error&msg=Erreur+SSH", server.ID), http.StatusSeeOther)
		return
	}

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

	// Exécuter l'action
	switch action {
	case "start":
		err = client.StartIncusContainer(ctx, container.IncusName)
		if err == nil {
			s.infraDB.UpdateContainerStatus(containerID, infra.ContainerStatusRunning)
		}
	case "stop":
		err = client.StopIncusContainer(ctx, container.IncusName)
		if err == nil {
			s.infraDB.UpdateContainerStatus(containerID, infra.ContainerStatusStopped)
		}
	case "restart":
		err = client.RestartIncusContainer(ctx, container.IncusName)
		if err == nil {
			s.infraDB.UpdateContainerStatus(containerID, infra.ContainerStatusRunning)
		}
	}

	if err != nil {
		msg := fmt.Sprintf("Erreur %s: %v", action, err)
		http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=error&msg=%s", server.ID, msg), http.StatusSeeOther)
		return
	}

	s.audit.LogAction(user.Username, "container_"+action, container.Name, map[string]any{
		"server": server.Name,
	})

	msg := fmt.Sprintf("Container %s: %s OK", container.Name, action)
	http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=success&msg=%s", server.ID, msg), http.StatusSeeOther)
}

// ============================================================================
// Nginx
// ============================================================================

// HandleNginxReload recharge Nginx sur un serveur.
func (s *AdminServer) HandleNginxReload(w http.ResponseWriter, r *http.Request) {
	session := GetSessionFromContext(r.Context())
	user := GetUserFromContext(r.Context())

	if session == nil || user == nil || !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	serverID, err := strconv.ParseInt(r.PathValue("serverID"), 10, 64)
	if err != nil {
		http.Error(w, "Invalid server ID", http.StatusBadRequest)
		return
	}

	server, err := s.infraDB.GetServer(serverID)
	if err != nil || server == nil {
		http.Error(w, "Server not found", http.StatusNotFound)
		return
	}

	if !server.HasNginx {
		http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=error&msg=Nginx+non+activé", serverID), http.StatusSeeOther)
		return
	}

	client, err := s.sshPool.Connect(server)
	if err != nil {
		http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=error&msg=Erreur+SSH", serverID), http.StatusSeeOther)
		return
	}

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

	if err := client.ReloadNginx(ctx); err != nil {
		msg := fmt.Sprintf("Erreur reload: %v", err)
		http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=error&msg=%s", serverID, msg), http.StatusSeeOther)
		return
	}

	s.audit.LogAction(user.Username, "nginx_reload", server.Name, nil)

	http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=success&msg=Nginx+rechargé", serverID), http.StatusSeeOther)
}

// HandleNginxSyncSites synchronise les sites Nginx depuis le serveur.
func (s *AdminServer) HandleNginxSyncSites(w http.ResponseWriter, r *http.Request) {
	session := GetSessionFromContext(r.Context())
	user := GetUserFromContext(r.Context())

	if session == nil || user == nil || !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	serverID, err := strconv.ParseInt(r.PathValue("serverID"), 10, 64)
	if err != nil {
		http.Error(w, "Invalid server ID", http.StatusBadRequest)
		return
	}

	server, err := s.infraDB.GetServer(serverID)
	if err != nil || server == nil {
		http.Error(w, "Server not found", http.StatusNotFound)
		return
	}

	if !server.HasNginx {
		http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=error&msg=Nginx+non+activé", serverID), http.StatusSeeOther)
		return
	}

	client, err := s.sshPool.Connect(server)
	if err != nil {
		http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=error&msg=Erreur+SSH", serverID), http.StatusSeeOther)
		return
	}

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

	sites, err := client.ListNginxSites(ctx)
	if err != nil {
		msg := fmt.Sprintf("Erreur liste sites: %v", err)
		http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=error&msg=%s", serverID, msg), http.StatusSeeOther)
		return
	}

	synced := 0
	for _, site := range sites {
		// Vérifier si existe déjà
		existing, _ := s.infraDB.GetNginxConfigByDomain(serverID, site.Name)
		if existing != nil {
			// Mettre à jour le statut
			status := infra.NginxConfigStatusInactive
			if site.Enabled {
				status = infra.NginxConfigStatusActive
			}
			existing.Status = status
			s.infraDB.UpdateNginxConfig(existing)
			continue
		}

		// Créer la config
		status := infra.NginxConfigStatusInactive
		if site.Enabled {
			status = infra.NginxConfigStatusActive
		}

		config := &infra.NginxConfig{
			ServerID: serverID,
			Domain:   site.Name,
			Type:     infra.NginxTypeProxy,
			Status:   status,
		}
		if err := s.infraDB.CreateNginxConfig(config); err == nil {
			synced++
		}
	}

	s.audit.LogAction(user.Username, "sync_nginx", server.Name, map[string]any{
		"synced": synced,
		"total":  len(sites),
	})

	msg := fmt.Sprintf("Sync OK: %d nouveaux sites", synced)
	http.Redirect(w, r, fmt.Sprintf("/admin/infra/servers/%d?flash=success&msg=%s", serverID, msg), http.StatusSeeOther)
}

// ============================================================================
// API (htmx)
// ============================================================================

// HandleAPIInfraStatus retourne le statut de l'infrastructure (partial htmx).
func (s *AdminServer) HandleAPIInfraStatus(w http.ResponseWriter, r *http.Request) {
	user := GetUserFromContext(r.Context())
	if user == nil || !user.IsSuperAdmin() {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	servers, _ := s.infraDB.ListServers()

	// Compter les statuts
	online := 0
	offline := 0
	for _, srv := range servers {
		if srv.Status == infra.ServerStatusOnline {
			online++
		} else if srv.Status == infra.ServerStatusOffline {
			offline++
		}
	}

	containers, _ := s.infraDB.ListAllContainers()
	running := 0
	stopped := 0
	for _, c := range containers {
		if c.Status == infra.ContainerStatusRunning {
			running++
		} else if c.Status == infra.ContainerStatusStopped {
			stopped++
		}
	}

	data := map[string]any{
		"ServerCount":    len(servers),
		"ServersOnline":  online,
		"ServersOffline": offline,
		"ContainerCount": len(containers),
		"Running":        running,
		"Stopped":        stopped,
	}

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