package web

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"gitea.dooplex.hu/admin/felhom-controller/internal/appexport"
	"gitea.dooplex.hu/admin/felhom-controller/internal/monitor"
	"gitea.dooplex.hu/admin/felhom-controller/internal/report"
	"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
	"gitea.dooplex.hu/admin/felhom-controller/internal/system"
)

// DebugCallbacks holds functions that need main.go wiring (modules not directly on Server).
type DebugCallbacks struct {
	TriggerHubReportPush  func() error
	TriggerSetupMode      func() error
	HubConnectivityTest   func() (statusCode int, latencyMs int64, err error)
	GiteaConnectivityTest func() (statusCode int, latencyMs int64, err error)
	GetTelemetryPreview   func() ([]report.AppTelemetry, error)
}

// debugPageHandler renders the debug dashboard page.
func (s *Server) debugPageHandler(w http.ResponseWriter, r *http.Request) {
	data := s.baseData("debug", "Debug")
	s.executeTemplate(w, r, "debug", data)
}

// handleDebugAPI dispatches /api/debug/* routes.
func (s *Server) handleDebugAPI(w http.ResponseWriter, r *http.Request) {
	subpath := strings.TrimPrefix(r.URL.Path, "/api/debug/")

	switch {
	// Section 1: Diagnostic dump
	case subpath == "dump" && r.Method == http.MethodGet:
		s.debugDump(w, r)

	// Section 2: Notification & Event testing
	case subpath == "event/test" && r.Method == http.MethodPost:
		s.debugTestEvent(w, r)
	case subpath == "event/history" && r.Method == http.MethodGet:
		s.debugEventHistory(w, r)

	// Section 3: Backup testing (app-data only; disk-tier moved to host agent)
	case subpath == "backup/dbdump" && r.Method == http.MethodPost:
		s.debugTriggerDBDump(w, r)

	// Section 5: Hub & connectivity
	case subpath == "hub/push" && r.Method == http.MethodPost:
		s.debugHubPush(w, r)
	case subpath == "hub/test-connectivity" && r.Method == http.MethodPost:
		s.debugHubConnectivity(w, r)
	case subpath == "hub/preferences-sync" && r.Method == http.MethodPost:
		s.debugPreferencesSync(w, r)
	case subpath == "gitea/test-connectivity" && r.Method == http.MethodPost:
		s.debugGiteaConnectivity(w, r)

	// Section: Telemetry testing
	case subpath == "telemetry" && r.Method == http.MethodGet:
		s.debugTelemetry(w, r)

	// Section 6: Self-update
	case subpath == "selfupdate/dry-run" && r.Method == http.MethodPost:
		s.debugSelfUpdateDryRun(w, r)

	// Section 7: DR / Setup
	case subpath == "dr/trigger-setup" && r.Method == http.MethodPost:
		s.debugTriggerSetupWizard(w, r)

	// Section 8: Log viewer
	case subpath == "logs" && r.Method == http.MethodGet:
		s.debugLogBuffer(w, r)

	// Section 9: App Export/Import
	case subpath == "appexport/status" && r.Method == http.MethodGet:
		s.debugAppExportStatus(w, r)
	case subpath == "appexport/bundles" && r.Method == http.MethodGet:
		s.debugAppExportBundles(w, r)
	case subpath == "appexport/cleanup" && r.Method == http.MethodPost:
		s.debugAppExportCleanup(w, r)

	default:
		http.NotFound(w, r)
	}
}

// writeDebugJSON writes a standard JSON response for debug endpoints.
func writeDebugJSON(w http.ResponseWriter, status int, ok bool, message string, data interface{}) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	resp := map[string]interface{}{
		"ok": ok,
	}
	if message != "" {
		if ok {
			resp["message"] = message
		} else {
			resp["error"] = message
		}
	}
	if data != nil {
		resp["data"] = data
	}
	json.NewEncoder(w).Encode(resp)
}

// ── Section 1: Diagnostic dump ──────────────────────────────────────

func (s *Server) debugDump(w http.ResponseWriter, r *http.Request) {
	dump := make(map[string]interface{})

	// Controller info
	configHash := ""
	configPath := s.cfg.Paths.DataDir // approximate; configPath isn't on Server
	if data, err := os.ReadFile(filepath.Join(filepath.Dir(configPath), "controller.yaml")); err == nil {
		h := sha256.Sum256(data)
		configHash = hex.EncodeToString(h[:])
	}
	dump["controller"] = map[string]interface{}{
		"version":        s.version,
		"uptime_seconds": int(time.Since(s.startTime).Seconds()),
		"config_hash":    configHash,
		"logging_level":  s.cfg.Logging.Level,
		"pid":            os.Getpid(),
	}

	// Storage
	storagePaths := s.settings.GetStoragePaths()
	storageEntries := make([]map[string]interface{}, 0, len(storagePaths))
	for _, sp := range storagePaths {
		entry := map[string]interface{}{
			"path":           sp.Path,
			"label":          sp.Label,
			"disconnected":   sp.Disconnected,
			"decommissioned": sp.Decommissioned,
		}
		if !sp.Disconnected && !sp.Decommissioned {
			if di := system.GetDiskUsage(sp.Path); di != nil {
				entry["total_gb"] = di.TotalGB
				entry["used_gb"] = di.UsedGB
				entry["used_percent"] = di.UsedPercent
			}
		}
		storageEntries = append(storageEntries, entry)
	}
	dump["storage"] = storageEntries

	// Stacks
	allStacks := s.stackMgr.GetStacks()
	deployed := 0
	running := 0
	stopped := 0
	stackList := make([]map[string]interface{}, 0)
	for _, st := range allStacks {
		if !st.Deployed {
			continue
		}
		deployed++
		info := map[string]interface{}{
			"name":  st.Name,
			"state": string(st.State),
		}
		if st.Meta.DisplayName != "" {
			info["display_name"] = st.Meta.DisplayName
		}
		containerNames := make([]string, 0, len(st.Containers))
		for _, c := range st.Containers {
			containerNames = append(containerNames, c.Name)
			switch c.State {
			case stacks.StateRunning, stacks.StateStarting, stacks.StateUnhealthy:
				running++
			default:
				stopped++
			}
		}
		info["containers"] = containerNames
		stackList = append(stackList, info)
	}
	dump["stacks"] = map[string]interface{}{
		"deployed": deployed,
		"running":  running,
		"stopped":  stopped,
		"list":     stackList,
	}

	// Backup
	if s.backupMgr != nil {
		backupInfo := map[string]interface{}{
			"enabled": true,
			"running": s.backupMgr.IsRunning(),
		}
		dbDump := s.backupMgr.GetStatus()
		if dbDump != nil {
			backupInfo["last_db_dump"] = map[string]interface{}{
				"time":    dbDump.LastRun,
				"success": dbDump.Success,
			}
		}
		dump["backup"] = backupInfo
	} else {
		dump["backup"] = map[string]interface{}{"enabled": false}
	}

	// Hub
	hubInfo := map[string]interface{}{
		"url":     s.cfg.Hub.URL,
		"enabled": s.cfg.Hub.Enabled,
	}
	if s.hubPushStatusFn != nil {
		st := s.hubPushStatusFn()
		hubInfo["last_attempt"] = st.LastAttempt
		hubInfo["last_success"] = st.LastSuccess
		hubInfo["last_error"] = st.LastError
		hubInfo["consecutive_failures"] = st.Consecutive
	}
	dump["hub"] = hubInfo

	// Scheduler
	if s.scheduler != nil {
		jobs := s.scheduler.GetJobs()
		jobList := make([]map[string]interface{}, 0, len(jobs))
		for _, j := range jobs {
			entry := map[string]interface{}{
				"name":    j.Name,
				"running": j.Running,
			}
			if j.Interval > 0 {
				entry["type"] = "every"
				entry["interval"] = j.Interval.String()
			} else if j.Schedule != "" {
				entry["type"] = "daily"
				entry["schedule"] = j.Schedule
			}
			if !j.LastRun.IsZero() {
				entry["last_run"] = j.LastRun
			}
			if j.LastErr != nil {
				entry["last_error"] = j.LastErr.Error()
			}
			jobList = append(jobList, entry)
		}
		dump["scheduler"] = jobList
	}

	// Health
	healthReport := monitor.RunHealthCheck(s.cfg, s.cpuCollector, storagePaths, s.logger)
	dump["health"] = map[string]interface{}{
		"status":   healthReport.Status,
		"issues":   healthReport.Issues,
		"warnings": healthReport.Warnings,
	}

	// Notifications
	prefs := s.settings.GetNotificationPrefs()
	dump["notifications"] = map[string]interface{}{
		"email":          prefs.Email,
		"enabled_events": prefs.EnabledEvents,
		"cooldown_hours": prefs.CooldownHours,
	}

	// Self-update
	if s.updater != nil {
		status := s.updater.GetStatus()
		dump["self_update"] = map[string]interface{}{
			"enabled":    true,
			"auto":       s.cfg.SelfUpdate.AutoUpdate,
			"last_check": status.LastCheck,
		}
	} else {
		dump["self_update"] = map[string]interface{}{"enabled": false}
	}

	// Alerts
	if s.alertManager != nil {
		dump["alerts"] = s.alertManager.GetAlerts()
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(dump)
}

// ── Section 2: Notification & Event testing ─────────────────────────

func (s *Server) debugTestEvent(w http.ResponseWriter, r *http.Request) {
	var req struct {
		EventType string `json:"event_type"`
		Severity  string `json:"severity"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeDebugJSON(w, http.StatusBadRequest, false, "Érvénytelen kérés", nil)
		return
	}
	if req.EventType == "" {
		req.EventType = "test"
	}
	if req.Severity == "" {
		req.Severity = "info"
	}

	if s.notifier == nil {
		writeDebugJSON(w, http.StatusBadRequest, false, "Notifier nincs konfigurálva", nil)
		return
	}

	statusCode, err := s.notifier.PushTestEventSync(req.EventType, req.Severity,
		fmt.Sprintf("Teszt esemény: %s (%s)", req.EventType, req.Severity))
	if err != nil {
		writeDebugJSON(w, http.StatusOK, false, err.Error(), map[string]interface{}{
			"hub_status": statusCode,
		})
		return
	}
	writeDebugJSON(w, http.StatusOK, true, fmt.Sprintf("Esemény elküldve (HTTP %d)", statusCode),
		map[string]interface{}{"hub_status": statusCode})
}

func (s *Server) debugEventHistory(w http.ResponseWriter, r *http.Request) {
	if s.notifier == nil {
		writeDebugJSON(w, http.StatusOK, true, "", []interface{}{})
		return
	}
	history := s.notifier.GetEventHistory(20)
	writeDebugJSON(w, http.StatusOK, true, "", history)
}

// ── Section 3: Backup testing ───────────────────────────────────────

func (s *Server) debugTriggerDBDump(w http.ResponseWriter, r *http.Request) {
	if s.backupMgr == nil {
		writeDebugJSON(w, http.StatusBadRequest, false, "Backup manager nincs konfigurálva", nil)
		return
	}
	go func() {
		if err := s.backupMgr.RunDBDumps(context.Background()); err != nil {
			s.logger.Printf("[WARN] Debug DB dump failed: %v", err)
		}
	}()
	writeDebugJSON(w, http.StatusOK, true, "DB dump elindítva", nil)
}

// ── Section 5: Hub & connectivity ───────────────────────────────────

func (s *Server) debugHubPush(w http.ResponseWriter, r *http.Request) {
	if s.debugCallbacks == nil || s.debugCallbacks.TriggerHubReportPush == nil {
		writeDebugJSON(w, http.StatusNotImplemented, false, "Nem bekötött", nil)
		return
	}
	start := time.Now()
	err := s.debugCallbacks.TriggerHubReportPush()
	latency := time.Since(start).Milliseconds()
	if err != nil {
		writeDebugJSON(w, http.StatusOK, false, err.Error(), map[string]interface{}{"latency_ms": latency})
		return
	}
	writeDebugJSON(w, http.StatusOK, true, "Hub jelentés elküldve",
		map[string]interface{}{"latency_ms": latency})
}

func (s *Server) debugHubConnectivity(w http.ResponseWriter, r *http.Request) {
	if s.debugCallbacks == nil || s.debugCallbacks.HubConnectivityTest == nil {
		writeDebugJSON(w, http.StatusNotImplemented, false, "Nem bekötött", nil)
		return
	}
	statusCode, latency, err := s.debugCallbacks.HubConnectivityTest()
	data := map[string]interface{}{
		"status_code": statusCode,
		"latency_ms":  latency,
	}
	if err != nil {
		writeDebugJSON(w, http.StatusOK, false, err.Error(), data)
		return
	}
	writeDebugJSON(w, http.StatusOK, true,
		fmt.Sprintf("Hub elérhető (HTTP %d, %dms)", statusCode, latency), data)
}

func (s *Server) debugPreferencesSync(w http.ResponseWriter, r *http.Request) {
	if s.notifier == nil {
		writeDebugJSON(w, http.StatusBadRequest, false, "Notifier nincs konfigurálva", nil)
		return
	}
	prefs := s.settings.GetNotificationPrefs()
	if err := s.notifier.SyncPreferences(prefs.Email, prefs.EnabledEvents, prefs.CooldownHours); err != nil {
		writeDebugJSON(w, http.StatusOK, false, err.Error(), nil)
		return
	}
	writeDebugJSON(w, http.StatusOK, true, "Preferenciák szinkronizálva", nil)
}

func (s *Server) debugGiteaConnectivity(w http.ResponseWriter, r *http.Request) {
	if s.debugCallbacks == nil || s.debugCallbacks.GiteaConnectivityTest == nil {
		writeDebugJSON(w, http.StatusNotImplemented, false, "Nem bekötött", nil)
		return
	}
	statusCode, latency, err := s.debugCallbacks.GiteaConnectivityTest()
	data := map[string]interface{}{
		"status_code": statusCode,
		"latency_ms":  latency,
	}
	if err != nil {
		writeDebugJSON(w, http.StatusOK, false, err.Error(), data)
		return
	}
	writeDebugJSON(w, http.StatusOK, true,
		fmt.Sprintf("Gitea elérhető (HTTP %d, %dms)", statusCode, latency), data)
}

// ── Section: Telemetry testing ───────────────────────────────────────

func (s *Server) debugTelemetry(w http.ResponseWriter, r *http.Request) {
	if s.debugCallbacks == nil || s.debugCallbacks.GetTelemetryPreview == nil {
		writeDebugJSON(w, http.StatusNotImplemented, false, "Nem bekötött", nil)
		return
	}
	start := time.Now()
	telemetry, err := s.debugCallbacks.GetTelemetryPreview()
	latency := time.Since(start).Milliseconds()
	if err != nil {
		writeDebugJSON(w, http.StatusOK, false, err.Error(), map[string]interface{}{"latency_ms": latency})
		return
	}

	totalErrors := 0
	totalWarnings := 0
	for _, app := range telemetry {
		totalErrors += app.LogErrors
		totalWarnings += app.LogWarnings
	}

	writeDebugJSON(w, http.StatusOK, true,
		fmt.Sprintf("Telemetria összegyűjtve: %d app, %d hiba, %d figyelmeztetés (%dms)",
			len(telemetry), totalErrors, totalWarnings, latency),
		map[string]interface{}{
			"latency_ms":     latency,
			"app_count":      len(telemetry),
			"total_errors":   totalErrors,
			"total_warnings": totalWarnings,
			"app_telemetry":  telemetry,
		})
}

// ── Section 6: Self-update ──────────────────────────────────────────

func (s *Server) debugSelfUpdateDryRun(w http.ResponseWriter, r *http.Request) {
	if s.updater == nil {
		writeDebugJSON(w, http.StatusBadRequest, false, "Self-update nincs konfigurálva", nil)
		return
	}
	result := s.updater.DryRun()
	writeDebugJSON(w, http.StatusOK, true, "", result)
}

// ── Section 7: DR / Setup ───────────────────────────────────────────

func (s *Server) debugTriggerSetupWizard(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Confirm string `json:"confirm"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeDebugJSON(w, http.StatusBadRequest, false, "Érvénytelen kérés", nil)
		return
	}
	if req.Confirm != "RESET" {
		writeDebugJSON(w, http.StatusBadRequest, false, "Érvénytelen megerősítés — írja be: RESET", nil)
		return
	}

	// Write marker file
	markerPath := filepath.Join(s.cfg.Paths.DataDir, ".needs-setup")
	if err := os.WriteFile(markerPath, []byte("debug-triggered\n"), 0644); err != nil {
		writeDebugJSON(w, http.StatusInternalServerError, false,
			fmt.Sprintf("Marker fájl írási hiba: %v", err), nil)
		return
	}

	writeDebugJSON(w, http.StatusOK, true, "Controller újraindítása setup módba...", nil)

	// Exit after response is sent so the container restarts into setup mode
	go func() {
		time.Sleep(500 * time.Millisecond)
		os.Exit(0)
	}()
}

// ── Section 8: Log viewer ───────────────────────────────────────────

func (s *Server) debugLogBuffer(w http.ResponseWriter, r *http.Request) {
	if s.logBuffer == nil {
		writeDebugJSON(w, http.StatusOK, true, "", map[string]interface{}{
			"entries": []interface{}{},
			"total":   0,
		})
		return
	}

	level := r.URL.Query().Get("level")
	if level == "" {
		level = "DEBUG"
	}

	limit := 200
	if v := r.URL.Query().Get("limit"); v != "" {
		if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 1000 {
			limit = n
		}
	}

	var after time.Time
	if v := r.URL.Query().Get("after"); v != "" {
		if t, err := time.Parse(time.RFC3339Nano, v); err == nil {
			after = t
		}
	}

	entries, total := s.logBuffer.Entries(level, limit, after)
	writeDebugJSON(w, http.StatusOK, true, "", map[string]interface{}{
		"entries": entries,
		"total":   total,
	})
}

// ── Section 9: App Export/Import ─────────────────────────────────────

func (s *Server) debugAppExportStatus(w http.ResponseWriter, r *http.Request) {
	if s.appExporter == nil {
		writeDebugJSON(w, http.StatusOK, true, "", map[string]interface{}{
			"available": false,
		})
		return
	}

	info := s.appExporter.GetDebugInfo()

	// Scan for bundles
	drives := s.storageDriveList()
	bundles := appexport.ScanForBundles(drives)

	// Scan for stale temp files
	staleFiles := appexport.ScanForStaleTempFiles(drives)

	info["bundle_count"] = len(bundles)
	info["stale_temp_files"] = staleFiles
	info["stale_temp_count"] = len(staleFiles)
	info["available"] = true

	// Export dirs
	exportDirs := make([]map[string]interface{}, 0, len(drives))
	for _, d := range drives {
		dir := appexport.ExportDir(d.Path)
		dirInfo := map[string]interface{}{
			"path":  dir,
			"label": d.Label,
		}
		if stat, err := os.Stat(dir); err == nil {
			dirInfo["exists"] = true
			dirInfo["modified"] = stat.ModTime()
		} else {
			dirInfo["exists"] = false
		}
		exportDirs = append(exportDirs, dirInfo)
	}
	info["export_dirs"] = exportDirs

	writeDebugJSON(w, http.StatusOK, true, "", info)
}

func (s *Server) debugAppExportBundles(w http.ResponseWriter, r *http.Request) {
	if s.appExporter == nil {
		writeDebugJSON(w, http.StatusBadRequest, false, "App export not available", nil)
		return
	}

	drives := s.storageDriveList()
	bundles := appexport.ScanForBundles(drives)

	writeDebugJSON(w, http.StatusOK, true,
		fmt.Sprintf("%d csomag található", len(bundles)),
		map[string]interface{}{"bundles": bundles})
}

func (s *Server) debugAppExportCleanup(w http.ResponseWriter, r *http.Request) {
	if s.appExporter == nil {
		writeDebugJSON(w, http.StatusBadRequest, false, "App export not available", nil)
		return
	}

	drives := s.storageDriveList()
	staleFiles := appexport.ScanForStaleTempFiles(drives)

	if len(staleFiles) == 0 {
		writeDebugJSON(w, http.StatusOK, true, "Nincs eltávolítandó temp fájl", nil)
		return
	}

	removed := 0
	for _, f := range staleFiles {
		if err := os.Remove(f); err != nil {
			s.logger.Printf("[WARN] Failed to remove stale temp file %s: %v", f, err)
		} else {
			s.logger.Printf("[INFO] Removed stale temp file: %s", f)
			removed++
		}
	}

	writeDebugJSON(w, http.StatusOK, true,
		fmt.Sprintf("%d/%d temp fájl eltávolítva", removed, len(staleFiles)), nil)
}
