package handlers

import (
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/Wikid82/charon/backend/internal/api/middleware"
	"github.com/Wikid82/charon/backend/internal/services"
	"github.com/Wikid82/charon/backend/internal/util"
	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

type BackupHandler struct {
	service         *services.BackupService
	securityService *services.SecurityService
	db              *gorm.DB
}

func NewBackupHandler(service *services.BackupService) *BackupHandler {
	return NewBackupHandlerWithDeps(service, nil, nil)
}

func NewBackupHandlerWithDeps(service *services.BackupService, securityService *services.SecurityService, db *gorm.DB) *BackupHandler {
	return &BackupHandler{service: service, securityService: securityService, db: db}
}

func (h *BackupHandler) List(c *gin.Context) {
	backups, err := h.service.ListBackups()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list backups"})
		return
	}
	c.JSON(http.StatusOK, backups)
}

func (h *BackupHandler) Create(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	filename, err := h.service.CreateBackup()
	if err != nil {
		middleware.GetRequestLogger(c).WithField("action", "create_backup").WithError(err).Error("Failed to create backup")
		if respondPermissionError(c, h.securityService, "backup_create_failed", err, h.service.BackupDir) {
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create backup: " + err.Error()})
		return
	}
	middleware.GetRequestLogger(c).WithField("action", "create_backup").WithField("filename", util.SanitizeForLog(filepath.Base(filename))).Info("Backup created successfully")
	c.JSON(http.StatusCreated, gin.H{"filename": filename, "message": "Backup created successfully"})
}

func (h *BackupHandler) Delete(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	filename := c.Param("filename")
	if err := h.service.DeleteBackup(filename); err != nil {
		if os.IsNotExist(err) {
			c.JSON(http.StatusNotFound, gin.H{"error": "Backup not found"})
			return
		}
		if respondPermissionError(c, h.securityService, "backup_delete_failed", err, h.service.BackupDir) {
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete backup"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"message": "Backup deleted"})
}

func (h *BackupHandler) Download(c *gin.Context) {
	filename := c.Param("filename")
	path, err := h.service.GetBackupPath(filename)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if _, err := os.Stat(path); os.IsNotExist(err) {
		c.JSON(http.StatusNotFound, gin.H{"error": "Backup not found"})
		return
	}

	c.Header("Content-Disposition", "attachment; filename="+filename)
	c.File(path)
}

func (h *BackupHandler) Restore(c *gin.Context) {
	if !requireAdmin(c) {
		return
	}

	filename := c.Param("filename")
	if err := h.service.RestoreBackup(filename); err != nil {
		// codeql[go/log-injection] Safe: User input sanitized via util.SanitizeForLog()
		// which removes control characters (0x00-0x1F, 0x7F) including CRLF
		middleware.GetRequestLogger(c).WithField("action", "restore_backup").WithField("filename", util.SanitizeForLog(filepath.Base(filename))).WithField("error", util.SanitizeForLog(err.Error())).Error("Failed to restore backup")
		if os.IsNotExist(err) {
			c.JSON(http.StatusNotFound, gin.H{"error": "Backup not found"})
			return
		}
		if respondPermissionError(c, h.securityService, "backup_restore_failed", err, h.service.BackupDir) {
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to restore backup: " + err.Error()})
		return
	}
	middleware.GetRequestLogger(c).WithField("action", "restore_backup").WithField("filename", util.SanitizeForLog(filepath.Base(filename))).Info("Backup restored successfully")

	restartRequired := true
	rehydrated := false

	if h.db != nil {
		var rehydrateErr error
		for attempt := 0; attempt < 5; attempt++ {
			rehydrateErr = h.service.RehydrateLiveDatabase(h.db)
			if rehydrateErr == nil {
				break
			}

			if !isSQLiteTransientRehydrateError(rehydrateErr) || attempt == 4 {
				break
			}

			time.Sleep(time.Duration(attempt+1) * 150 * time.Millisecond)
		}

		if rehydrateErr != nil {
			middleware.GetRequestLogger(c).WithField("action", "restore_backup_rehydrate").WithError(rehydrateErr).Warn("Backup restored but live database rehydrate failed")
		} else {
			restartRequired = false
			rehydrated = true
		}
	}

	c.JSON(http.StatusOK, gin.H{
		"message":                "Backup restored successfully",
		"restart_required":       restartRequired,
		"live_rehydrate_applied": rehydrated,
	})
}

func isSQLiteTransientRehydrateError(err error) bool {
	if err == nil {
		return false
	}

	message := strings.ToLower(err.Error())
	return strings.Contains(message, "database is locked") ||
		strings.Contains(message, "database is busy") ||
		strings.Contains(message, "database table is locked") ||
		strings.Contains(message, "table is locked") ||
		strings.Contains(message, "resource busy")
}
