package handlers

import (
	"fmt"
	"io"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"gorm.io/gorm"

	"github.com/Wikid82/charon/backend/internal/logger"
	"github.com/Wikid82/charon/backend/internal/models"
	"github.com/Wikid82/charon/backend/internal/services"
	"github.com/Wikid82/charon/backend/internal/util"
)

// BackupServiceInterface defines the contract for backup service operations
type BackupServiceInterface interface {
	CreateBackup() (string, error)
	ListBackups() ([]services.BackupFile, error)
	DeleteBackup(filename string) error
	GetBackupPath(filename string) (string, error)
	RestoreBackup(filename string) error
	GetAvailableSpace() (int64, error)
}

type CertificateHandler struct {
	service             *services.CertificateService
	backupService       BackupServiceInterface
	notificationService *services.NotificationService
	db                  *gorm.DB
	// Rate limiting for notifications
	notificationMu       sync.Mutex
	lastNotificationTime map[string]time.Time
}

func NewCertificateHandler(service *services.CertificateService, backupService BackupServiceInterface, ns *services.NotificationService) *CertificateHandler {
	return &CertificateHandler{
		service:              service,
		backupService:        backupService,
		notificationService:  ns,
		lastNotificationTime: make(map[string]time.Time),
	}
}

// SetDB sets the database connection for user lookups (export re-auth).
func (h *CertificateHandler) SetDB(db *gorm.DB) {
	h.db = db
}

// maxFileSize is 1MB for certificate file uploads.
const maxFileSize = 1 << 20

func (h *CertificateHandler) List(c *gin.Context) {
	certs, err := h.service.ListCertificates()
	if err != nil {
		logger.Log().WithError(err).Error("failed to list certificates")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list certificates"})
		return
	}

	c.JSON(http.StatusOK, certs)
}

func (h *CertificateHandler) Get(c *gin.Context) {
	certUUID := c.Param("uuid")
	if certUUID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "uuid is required"})
		return
	}

	detail, err := h.service.GetCertificate(certUUID)
	if err != nil {
		if err == services.ErrCertNotFound {
			c.JSON(http.StatusNotFound, gin.H{"error": "certificate not found"})
			return
		}
		logger.Log().WithError(err).Error("failed to get certificate")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get certificate"})
		return
	}

	c.JSON(http.StatusOK, detail)
}

func (h *CertificateHandler) Upload(c *gin.Context) {
	name := c.PostForm("name")
	if name == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "name is required"})
		return
	}

	// Read certificate file
	certFile, err := c.FormFile("certificate_file")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "certificate_file is required"})
		return
	}

	certSrc, err := certFile.Open()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to open cert file"})
		return
	}
	defer func() {
		if errClose := certSrc.Close(); errClose != nil {
			logger.Log().WithError(errClose).Warn("failed to close certificate file")
		}
	}()

	certBytes, err := io.ReadAll(io.LimitReader(certSrc, maxFileSize))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read certificate file"})
		return
	}
	certPEM := string(certBytes)

	// Read private key file (optional — format detection is content-based in the service)
	var keyPEM string
	keyFile, err := c.FormFile("key_file")
	if err == nil {
		keySrc, errOpen := keyFile.Open()
		if errOpen != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to open key file"})
			return
		}
		defer func() {
			if errClose := keySrc.Close(); errClose != nil {
				logger.Log().WithError(errClose).Warn("failed to close key file")
			}
		}()

		keyBytes, errRead := io.ReadAll(io.LimitReader(keySrc, maxFileSize))
		if errRead != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read key file"})
			return
		}
		keyPEM = string(keyBytes)
	}

	// Read chain file (optional)
	var chainPEM string
	chainFile, err := c.FormFile("chain_file")
	if err == nil {
		chainSrc, errOpen := chainFile.Open()
		if errOpen != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to open chain file"})
			return
		}
		defer func() {
			if errClose := chainSrc.Close(); errClose != nil {
				logger.Log().WithError(errClose).Warn("failed to close chain file")
			}
		}()

		chainBytes, errRead := io.ReadAll(io.LimitReader(chainSrc, maxFileSize))
		if errRead != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read chain file"})
			return
		}
		chainPEM = string(chainBytes)
	}

	// Require key_file for non-PFX formats (PFX embeds the private key)
	if keyPEM == "" {
		format := services.DetectFormat(certBytes)
		if format != services.FormatPFX {
			c.JSON(http.StatusBadRequest, gin.H{"error": "key_file is required for PEM/DER certificate uploads"})
			return
		}
	}

	cert, err := h.service.UploadCertificate(name, certPEM, keyPEM, chainPEM)
	if err != nil {
		logger.Log().WithError(err).Error("failed to upload certificate")
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if h.notificationService != nil {
		h.notificationService.SendExternal(c.Request.Context(),
			"cert",
			"Certificate Uploaded",
			"A new custom certificate was successfully uploaded.",
			map[string]any{
				"Name":    util.SanitizeForLog(cert.Name),
				"Domains": util.SanitizeForLog(cert.Domains),
				"Action":  "uploaded",
			},
		)
	}

	c.JSON(http.StatusCreated, cert)
}

type updateCertificateRequest struct {
	Name string `json:"name" binding:"required"`
}

func (h *CertificateHandler) Update(c *gin.Context) {
	certUUID := c.Param("uuid")
	if certUUID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "uuid is required"})
		return
	}

	var req updateCertificateRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "name is required"})
		return
	}

	info, err := h.service.UpdateCertificate(certUUID, req.Name)
	if err != nil {
		if err == services.ErrCertNotFound {
			c.JSON(http.StatusNotFound, gin.H{"error": "certificate not found"})
			return
		}
		logger.Log().WithError(err).Error("failed to update certificate")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update certificate"})
		return
	}

	c.JSON(http.StatusOK, info)
}

func (h *CertificateHandler) Validate(c *gin.Context) {
	// Read certificate file
	certFile, err := c.FormFile("certificate_file")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "certificate_file is required"})
		return
	}

	certSrc, err := certFile.Open()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to open cert file"})
		return
	}
	defer func() {
		if errClose := certSrc.Close(); errClose != nil {
			logger.Log().WithError(errClose).Warn("failed to close certificate file")
		}
	}()

	certBytes, err := io.ReadAll(io.LimitReader(certSrc, maxFileSize))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read certificate file"})
		return
	}

	// Read optional key file
	var keyPEM string
	keyFile, err := c.FormFile("key_file")
	if err == nil {
		keySrc, errOpen := keyFile.Open()
		if errOpen != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to open key file"})
			return
		}
		defer func() {
			if errClose := keySrc.Close(); errClose != nil {
				logger.Log().WithError(errClose).Warn("failed to close key file")
			}
		}()

		keyBytes, errRead := io.ReadAll(io.LimitReader(keySrc, maxFileSize))
		if errRead != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read key file"})
			return
		}
		keyPEM = string(keyBytes)
	}

	// Read optional chain file
	var chainPEM string
	chainFile, err := c.FormFile("chain_file")
	if err == nil {
		chainSrc, errOpen := chainFile.Open()
		if errOpen != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to open chain file"})
			return
		}
		defer func() {
			if errClose := chainSrc.Close(); errClose != nil {
				logger.Log().WithError(errClose).Warn("failed to close chain file")
			}
		}()

		chainBytes, errRead := io.ReadAll(io.LimitReader(chainSrc, maxFileSize))
		if errRead != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read chain file"})
			return
		}
		chainPEM = string(chainBytes)
	}

	result, err := h.service.ValidateCertificate(string(certBytes), keyPEM, chainPEM)
	if err != nil {
		logger.Log().WithError(err).Error("failed to validate certificate")
		c.JSON(http.StatusBadRequest, gin.H{
			"error":  "validation failed",
			"errors": []string{err.Error()},
		})
		return
	}

	c.JSON(http.StatusOK, result)
}

type exportCertificateRequest struct {
	Format      string `json:"format" binding:"required"`
	IncludeKey  bool   `json:"include_key"`
	PFXPassword string `json:"pfx_password"`
	Password    string `json:"password"`
}

func (h *CertificateHandler) Export(c *gin.Context) {
	certUUID := c.Param("uuid")
	if certUUID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "uuid is required"})
		return
	}

	var req exportCertificateRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "format is required"})
		return
	}

	// Re-authenticate when requesting private key
	if req.IncludeKey {
		if req.Password == "" {
			c.JSON(http.StatusForbidden, gin.H{"error": "password required to export private key"})
			return
		}

		userVal, exists := c.Get("user")
		if !exists || h.db == nil {
			c.JSON(http.StatusForbidden, gin.H{"error": "authentication required"})
			return
		}

		userMap, ok := userVal.(map[string]any)
		if !ok {
			c.JSON(http.StatusForbidden, gin.H{"error": "invalid session"})
			return
		}

		userID, ok := userMap["id"]
		if !ok {
			c.JSON(http.StatusForbidden, gin.H{"error": "invalid session"})
			return
		}

		var user models.User
		if err := h.db.First(&user, userID).Error; err != nil {
			c.JSON(http.StatusForbidden, gin.H{"error": "user not found"})
			return
		}

		if !user.CheckPassword(req.Password) {
			c.JSON(http.StatusForbidden, gin.H{"error": "incorrect password"})
			return
		}
	}

	data, filename, err := h.service.ExportCertificate(certUUID, req.Format, req.IncludeKey, req.PFXPassword)
	if err != nil {
		if err == services.ErrCertNotFound {
			c.JSON(http.StatusNotFound, gin.H{"error": "certificate not found"})
			return
		}
		logger.Log().WithError(fmt.Errorf("%s", util.SanitizeForLog(err.Error()))).Error("failed to export certificate")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to export certificate"})
		return
	}

	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
	c.Data(http.StatusOK, "application/octet-stream", data)
}

func (h *CertificateHandler) Delete(c *gin.Context) {
	idStr := c.Param("uuid")

	// Support both numeric ID (legacy) and UUID
	if numID, err := strconv.ParseUint(idStr, 10, 32); err == nil && numID > 0 {
		inUse, err := h.service.IsCertificateInUse(uint(numID))
		if err != nil {
			logger.Log().WithError(err).WithField("certificate_id", numID).Error("failed to check certificate usage")
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to check certificate usage"})
			return
		}
		if inUse {
			c.JSON(http.StatusConflict, gin.H{"error": "certificate is in use by one or more proxy hosts"})
			return
		}

		if h.backupService != nil {
			if availableSpace, err := h.backupService.GetAvailableSpace(); err != nil {
				logger.Log().WithError(err).Warn("unable to check disk space, proceeding with backup")
			} else if availableSpace < 100*1024*1024 {
				logger.Log().WithField("available_bytes", availableSpace).Warn("low disk space, skipping backup")
				c.JSON(http.StatusInsufficientStorage, gin.H{"error": "insufficient disk space for backup"})
				return
			}

			if _, err := h.backupService.CreateBackup(); err != nil {
				logger.Log().WithError(err).Error("failed to create backup before deletion")
				c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create backup before deletion"})
				return
			}
		}

		if err := h.service.DeleteCertificateByID(uint(numID)); err != nil {
			if err == services.ErrCertInUse {
				c.JSON(http.StatusConflict, gin.H{"error": "certificate is in use by one or more proxy hosts"})
				return
			}
			logger.Log().WithError(err).WithField("certificate_id", numID).Error("failed to delete certificate")
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete certificate"})
			return
		}

		h.sendDeleteNotification(c, fmt.Sprintf("%d", numID))
		c.JSON(http.StatusOK, gin.H{"message": "certificate deleted"})
		return
	}

	// UUID path - parse to validate format and produce a canonical, safe string
	parsedUUID, parseErr := uuid.Parse(idStr)
	if parseErr != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
		return
	}
	certUUID := parsedUUID.String()

	inUse, err := h.service.IsCertificateInUseByUUID(certUUID)
	if err != nil {
		if err == services.ErrCertNotFound {
			c.JSON(http.StatusNotFound, gin.H{"error": "certificate not found"})
			return
		}
		logger.Log().WithError(err).WithField("certificate_uuid", util.SanitizeForLog(certUUID)).Error("failed to check certificate usage")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to check certificate usage"})
		return
	}
	if inUse {
		c.JSON(http.StatusConflict, gin.H{"error": "certificate is in use by one or more proxy hosts"})
		return
	}

	if h.backupService != nil {
		if availableSpace, err := h.backupService.GetAvailableSpace(); err != nil {
			logger.Log().WithError(err).Warn("unable to check disk space, proceeding with backup")
		} else if availableSpace < 100*1024*1024 {
			c.JSON(http.StatusInsufficientStorage, gin.H{"error": "insufficient disk space for backup"})
			return
		}

		if _, err := h.backupService.CreateBackup(); err != nil {
			logger.Log().WithError(err).Error("failed to create backup before deletion")
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create backup before deletion"})
			return
		}
	}

	if err := h.service.DeleteCertificate(certUUID); err != nil {
		if err == services.ErrCertInUse {
			c.JSON(http.StatusConflict, gin.H{"error": "certificate is in use by one or more proxy hosts"})
			return
		}
		if err == services.ErrCertNotFound {
			c.JSON(http.StatusNotFound, gin.H{"error": "certificate not found"})
			return
		}
		logger.Log().WithError(err).WithField("certificate_uuid", util.SanitizeForLog(certUUID)).Error("failed to delete certificate")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete certificate"})
		return
	}

	h.sendDeleteNotification(c, certUUID)
	c.JSON(http.StatusOK, gin.H{"message": "certificate deleted"})
}

func (h *CertificateHandler) sendDeleteNotification(c *gin.Context, certRef string) {
	if h.notificationService == nil {
		return
	}

	// Re-validate to produce a CodeQL-safe value (breaks taint from user input).
	// Callers already pass validated data; this is defense-in-depth.
	safeRef := sanitizeCertRef(certRef)

	h.notificationMu.Lock()
	lastTime, exists := h.lastNotificationTime[certRef]
	if exists && time.Since(lastTime) < 10*time.Second {
		h.notificationMu.Unlock()
		logger.Log().WithField("certificate_ref", safeRef).Debug("notification rate limited")
		return
	}
	h.lastNotificationTime[certRef] = time.Now()
	h.notificationMu.Unlock()

	h.notificationService.SendExternal(c.Request.Context(),
		"cert",
		"Certificate Deleted",
		fmt.Sprintf("Certificate %s deleted", safeRef),
		map[string]any{
			"Ref":    safeRef,
			"Action": "deleted",
		},
	)
}

// sanitizeCertRef re-validates a certificate reference (UUID or numeric ID)
// and returns a safe string representation. Returns a placeholder if invalid.
func sanitizeCertRef(ref string) string {
	if parsed, err := uuid.Parse(ref); err == nil {
		return parsed.String()
	}
	if n, err := strconv.ParseUint(ref, 10, 64); err == nil {
		return strconv.FormatUint(n, 10)
	}
	return "[invalid-ref]"
}
