//go:build !headless && !lite

package admin

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"breadbox/internal/app"
	"breadbox/internal/db"
	"breadbox/internal/pgconv"
	"breadbox/internal/service"
	"breadbox/internal/templates/components/pages"

	"github.com/alexedwards/scs/v2"
	"github.com/go-chi/chi/v5"
)

// BackupsPageHandler serves GET /backups — the backup management page.
func BackupsPageHandler(a *app.App, sm *scs.SessionManager, tr *TemplateRenderer) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()

		if a.BackupService == nil {
			data := BaseTemplateData(r, sm, "backups", "Backups")
			data["Error"] = "Backup service is not available. pg_dump may not be installed."
			props := pages.BackupsProps{
				CSRFToken: GetCSRFToken(r),
				Error:     "Backup service is not available. pg_dump may not be installed.",
			}
			renderBackups(w, r, sm, tr, data, props)
			return
		}

		backups, err := a.BackupService.ListBackups()
		if err != nil {
			a.Logger.Error("list backups", "error", err)
			backups = nil
		}

		totalSize, _ := a.BackupService.TotalBackupSize()

		schedule := a.Service.GetBackupSchedule(ctx)
		retentionDays := a.Service.GetBackupRetentionDays(ctx)
		preflight := a.BackupService.Preflight(ctx)

		data := BaseTemplateData(r, sm, "backups", "Backups")
		data["Backups"] = backups
		data["BackupCount"] = len(backups)
		data["TotalSize"] = service.FormatBytes(totalSize)
		data["Schedule"] = schedule
		data["RetentionDays"] = retentionDays
		data["HasEncryptionKey"] = len(a.Config.EncryptionKey) > 0
		data["DatabaseName"] = service.ParseDatabaseName(a.Config.DatabaseURL)
		data["BackupDir"] = a.BackupService.BackupDir()
		data["PreflightOK"] = preflight.OK
		data["PreflightMessage"] = preflight.Message

		props := pages.BackupsProps{
			CSRFToken:        GetCSRFToken(r),
			HasEncryptionKey: len(a.Config.EncryptionKey) > 0,
			BackupCount:      len(backups),
			TotalSize:        service.FormatBytes(totalSize),
			Schedule:         schedule,
			RetentionDays:    retentionDays,
			BackupDir:        a.BackupService.BackupDir(),
			PreflightOK:      preflight.OK,
			PreflightMessage: preflight.Message,
			Backups:          make([]pages.BackupRow, 0, len(backups)),
		}
		for _, b := range backups {
			props.Backups = append(props.Backups, pages.BackupRow{
				Filename:      b.Filename,
				SizeFormatted: service.FormatBytes(b.Size),
				CreatedAtRel:  relativeTime(b.CreatedAt),
				Trigger:       b.Trigger,
				DownloadHref:  "/-/backups/" + b.Filename + "/download",
				RestoreAction: "/-/backups/" + b.Filename + "/restore",
				DeleteAction:  "/-/backups/" + b.Filename + "/delete",
			})
		}

		renderBackups(w, r, sm, tr, data, props)
	}
}

// renderBackups wraps the Backups tab body in the unified Settings shell.
func renderBackups(w http.ResponseWriter, r *http.Request, sm *scs.SessionManager, tr *TemplateRenderer, data map[string]any, props pages.BackupsProps) {
	renderSettingsTab(tr, w, r, data, pages.SettingsTabBackups, pages.Backups(props))
}

// CreateBackupHandler serves POST /-/backups/create — triggers a manual backup.
func CreateBackupHandler(a *app.App, sm *scs.SessionManager) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()

		if a.BackupService == nil {
			FlashRedirect(w, r, sm, "error", "Backup service is not available.", "/settings/backups")
			return
		}

		filename, err := a.BackupService.CreateBackup(ctx, "manual")
		if err != nil {
			a.Logger.Error("create backup", "error", err)
			FlashRedirect(w, r, sm, "error", "Failed to create backup: "+err.Error(), "/settings/backups")
			return
		}

		SetFlash(ctx, sm, "success", fmt.Sprintf("Backup created: %s", filename))
		http.Redirect(w, r, "/settings/backups", http.StatusSeeOther)
	}
}

// DownloadBackupHandler serves GET /-/backups/{filename}/download.
func DownloadBackupHandler(a *app.App) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if a.BackupService == nil {
			http.Error(w, "Backup service not available", http.StatusServiceUnavailable)
			return
		}

		filename := chi.URLParam(r, "filename")
		fullPath, err := a.BackupService.GetBackupPath(filename)
		if err != nil {
			http.Error(w, "Backup not found", http.StatusNotFound)
			return
		}

		w.Header().Set("Content-Type", "application/gzip")
		w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
		http.ServeFile(w, r, fullPath)
	}
}

// DeleteBackupHandler serves DELETE /-/backups/{filename}.
func DeleteBackupHandler(a *app.App, sm *scs.SessionManager) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()

		if a.BackupService == nil {
			FlashRedirect(w, r, sm, "error", "Backup service not available.", "/settings/backups")
			return
		}

		filename := chi.URLParam(r, "filename")
		if err := a.BackupService.DeleteBackup(filename); err != nil {
			a.Logger.Error("delete backup", "error", err, "filename", filename)
			FlashRedirect(w, r, sm, "error", "Failed to delete backup.", "/settings/backups")
			return
		}

		SetFlash(ctx, sm, "success", fmt.Sprintf("Backup deleted: %s", filename))
		http.Redirect(w, r, "/settings/backups", http.StatusSeeOther)
	}
}

// RestoreBackupHandler serves POST /-/backups/restore — restores from an uploaded file.
func RestoreBackupHandler(a *app.App, sm *scs.SessionManager) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()

		if a.BackupService == nil {
			FlashRedirect(w, r, sm, "error", "Backup service not available.", "/settings/backups")
			return
		}

		// Limit upload to 500MB.
		r.Body = http.MaxBytesReader(w, r.Body, 500<<20)

		source := r.FormValue("restore_source")

		switch source {
		case "upload":
			file, header, err := r.FormFile("backup_file")
			if err != nil {
				FlashRedirect(w, r, sm, "error", "No backup file provided.", "/settings/backups")
				return
			}
			defer file.Close()

			if !strings.HasSuffix(header.Filename, ".sql.gz") {
				FlashRedirect(w, r, sm, "error", "Invalid file type. Only .sql.gz files are supported.", "/settings/backups")
				return
			}

			if err := a.BackupService.RestoreFromReader(ctx, file); err != nil {
				a.Logger.Error("restore from upload", "error", err)
				FlashRedirect(w, r, sm, "error", "Restore failed: "+err.Error(), "/settings/backups")
				return
			}

			SetFlash(ctx, sm, "success", "Database restored successfully from uploaded file. You may need to restart the server.")

		case "existing":
			filename := r.FormValue("backup_filename")
			if filename == "" {
				FlashRedirect(w, r, sm, "error", "No backup file selected.", "/settings/backups")
				return
			}

			if err := a.BackupService.RestoreBackup(ctx, filename); err != nil {
				a.Logger.Error("restore from existing", "error", err, "filename", filename)
				FlashRedirect(w, r, sm, "error", "Restore failed: "+err.Error(), "/settings/backups")
				return
			}

			SetFlash(ctx, sm, "success", fmt.Sprintf("Database restored from %s. You may need to restart the server.", filename))

		default:
			SetFlash(ctx, sm, "error", "Invalid restore source.")
		}

		http.Redirect(w, r, "/settings/backups", http.StatusSeeOther)
	}
}

// BackupScheduleHandler serves POST /-/backups/schedule — saves backup schedule settings.
func BackupScheduleHandler(a *app.App, sm *scs.SessionManager) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()

		schedule := r.FormValue("backup_schedule")
		retentionStr := r.FormValue("backup_retention_days")

		// Validate schedule.
		validSchedules := map[string]bool{
			"":          true, // disabled
			"daily_2am": true,
			"daily_3am": true,
			"daily_4am": true,
			"weekly":    true,
		}
		if !validSchedules[schedule] {
			FlashRedirect(w, r, sm, "error", "Invalid backup schedule.", "/settings/backups")
			return
		}

		// Validate retention.
		retentionDays, err := strconv.Atoi(retentionStr)
		if err != nil || retentionDays < 1 || retentionDays > 365 {
			FlashRedirect(w, r, sm, "error", "Invalid retention period. Must be 1-365 days.", "/settings/backups")
			return
		}

		// Save schedule.
		if err := a.Queries.SetAppConfig(ctx, db.SetAppConfigParams{
			Key:   "backup_schedule",
			Value: pgconv.Text(schedule),
		}); err != nil {
			a.Logger.Error("save backup schedule", "error", err)
			FlashRedirect(w, r, sm, "error", "Failed to save backup schedule.", "/settings/backups")
			return
		}

		// Save retention.
		if err := a.Queries.SetAppConfig(ctx, db.SetAppConfigParams{
			Key:   "backup_retention_days",
			Value: pgconv.Text(strconv.Itoa(retentionDays)),
		}); err != nil {
			a.Logger.Error("save backup retention", "error", err)
			FlashRedirect(w, r, sm, "error", "Failed to save retention setting.", "/settings/backups")
			return
		}

		if schedule == "" {
			SetFlash(ctx, sm, "success", "Scheduled backups disabled.")
		} else {
			SetFlash(ctx, sm, "success", fmt.Sprintf("Backup schedule saved. Retention: %d days.", retentionDays))
		}
		http.Redirect(w, r, "/settings/backups", http.StatusSeeOther)
	}
}

// RestoreExistingBackupHandler serves POST /-/backups/{filename}/restore.
func RestoreExistingBackupHandler(a *app.App, sm *scs.SessionManager) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()

		if a.BackupService == nil {
			FlashRedirect(w, r, sm, "error", "Backup service not available.", "/settings/backups")
			return
		}

		filename := chi.URLParam(r, "filename")
		if err := a.BackupService.RestoreBackup(ctx, filename); err != nil {
			a.Logger.Error("restore backup", "error", err, "filename", filename)
			FlashRedirect(w, r, sm, "error", "Restore failed: "+err.Error(), "/settings/backups")
			return
		}

		SetFlash(ctx, sm, "success", fmt.Sprintf("Database restored from %s. You may need to restart the server.", filename))
		http.Redirect(w, r, "/settings/backups", http.StatusSeeOther)
	}
}
