package handlers

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

	"github.com/Fimeg/RedFlag/aggregator-server/internal/command"
	"github.com/Fimeg/RedFlag/aggregator-server/internal/database/queries"
	"github.com/Fimeg/RedFlag/aggregator-server/internal/models"
	"github.com/Fimeg/RedFlag/aggregator-server/internal/services"
	"github.com/Fimeg/RedFlag/aggregator-server/internal/logging"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

type SubsystemHandler struct {
	subsystemQueries *queries.SubsystemQueries
	commandQueries   *queries.CommandQueries
	commandFactory   *command.Factory
	signingService   *services.SigningService
	securityLogger   *logging.SecurityLogger
}

func NewSubsystemHandler(sq *queries.SubsystemQueries, cq *queries.CommandQueries, cf *command.Factory, signingService *services.SigningService, securityLogger *logging.SecurityLogger) *SubsystemHandler {
	return &SubsystemHandler{
		subsystemQueries: sq,
		commandQueries:   cq,
		commandFactory:   cf,
		signingService:   signingService,
		securityLogger:   securityLogger,
	}
}

// signAndCreateCommand signs a command if signing service is enabled, then stores it in the database
func (h *SubsystemHandler) signAndCreateCommand(cmd *models.AgentCommand) error {
	// Generate ID if not set (prevents zero UUID issues)
	if cmd.ID == uuid.Nil {
		cmd.ID = uuid.New()
	}

	// Set timestamps if not set
	if cmd.CreatedAt.IsZero() {
		cmd.CreatedAt = time.Now()
	}
	if cmd.UpdatedAt.IsZero() {
		cmd.UpdatedAt = time.Now()
	}

	// Sign the command before storing
	if h.signingService != nil && h.signingService.IsEnabled() {
		signature, err := h.signingService.SignCommand(cmd)
		if err != nil {
			return fmt.Errorf("failed to sign command: %w", err)
		}
		cmd.Signature = signature

		// Log successful signing
		if h.securityLogger != nil {
			h.securityLogger.LogCommandSigned(cmd)
		}
	} else {
		// Log warning if signing disabled
		log.Printf("[WARNING] Command signing disabled, storing unsigned command")
		if h.securityLogger != nil {
			h.securityLogger.LogPrivateKeyNotConfigured()
		}
	}

	// Store in database
	err := h.commandQueries.CreateCommand(cmd)
	if err != nil {
		return fmt.Errorf("failed to create command: %w", err)
	}

	return nil
}

// GetSubsystems retrieves all subsystems for an agent
// GET /api/v1/agents/:id/subsystems
func (h *SubsystemHandler) GetSubsystems(c *gin.Context) {
	agentID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
		return
	}

	subsystems, err := h.subsystemQueries.GetSubsystems(agentID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve subsystems"})
		return
	}

	c.JSON(http.StatusOK, subsystems)
}

// GetSubsystem retrieves a specific subsystem for an agent
// GET /api/v1/agents/:id/subsystems/:subsystem
func (h *SubsystemHandler) GetSubsystem(c *gin.Context) {
	agentID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
		return
	}

	subsystem := c.Param("subsystem")
	if subsystem == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Subsystem name required"})
		return
	}

	sub, err := h.subsystemQueries.GetSubsystem(agentID, subsystem)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve subsystem"})
		return
	}

	if sub == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Subsystem not found"})
		return
	}

	c.JSON(http.StatusOK, sub)
}

// UpdateSubsystem updates subsystem configuration
// PATCH /api/v1/agents/:id/subsystems/:subsystem
func (h *SubsystemHandler) UpdateSubsystem(c *gin.Context) {
	agentID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
		return
	}

	subsystem := c.Param("subsystem")
	if subsystem == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Subsystem name required"})
		return
	}

	var config models.SubsystemConfig
	if err := c.ShouldBindJSON(&config); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Validate interval if provided
	if config.IntervalMinutes != nil && (*config.IntervalMinutes < 5 || *config.IntervalMinutes > 1440) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Interval must be between 5 and 1440 minutes"})
		return
	}

	err = h.subsystemQueries.UpdateSubsystem(agentID, subsystem, config)
	if err != nil {
		if err.Error() == "subsystem not found" {
			c.JSON(http.StatusNotFound, gin.H{"error": "Subsystem not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update subsystem"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Subsystem updated successfully"})
}

// EnableSubsystem enables a subsystem
// POST /api/v1/agents/:id/subsystems/:subsystem/enable
func (h *SubsystemHandler) EnableSubsystem(c *gin.Context) {
	agentID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
		return
	}

	subsystem := c.Param("subsystem")
	if subsystem == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Subsystem name required"})
		return
	}

	err = h.subsystemQueries.EnableSubsystem(agentID, subsystem)
	if err != nil {
		if err.Error() == "subsystem not found" {
			c.JSON(http.StatusNotFound, gin.H{"error": "Subsystem not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to enable subsystem"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Subsystem enabled successfully"})
}

// DisableSubsystem disables a subsystem
// POST /api/v1/agents/:id/subsystems/:subsystem/disable
func (h *SubsystemHandler) DisableSubsystem(c *gin.Context) {
	agentID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
		return
	}

	subsystem := c.Param("subsystem")
	if subsystem == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Subsystem name required"})
		return
	}

	err = h.subsystemQueries.DisableSubsystem(agentID, subsystem)
	if err != nil {
		if err.Error() == "subsystem not found" {
			c.JSON(http.StatusNotFound, gin.H{"error": "Subsystem not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to disable subsystem"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Subsystem disabled successfully"})
}

// TriggerSubsystem manually triggers a subsystem scan
// POST /api/v1/agents/:id/subsystems/:subsystem/trigger
func (h *SubsystemHandler) TriggerSubsystem(c *gin.Context) {
	agentID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
		return
	}

	subsystem := c.Param("subsystem")
	if subsystem == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Subsystem name required"})
		return
	}

	// Verify subsystem exists and is enabled
	sub, err := h.subsystemQueries.GetSubsystem(agentID, subsystem)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve subsystem"})
		return
	}

	if sub == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Subsystem not found"})
		return
	}

	if !sub.Enabled {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Subsystem is disabled"})
		return
	}

	commandType := "scan_" + subsystem
	idempotencyKey := fmt.Sprintf("%s_%s_%d", agentID.String(), subsystem, time.Now().Unix())

	// Log command creation attempt
	log.Printf("[INFO] [server] [command] creating_scan_command agent_id=%s subsystem=%s command_type=%s timestamp=%s",
		agentID, subsystem, commandType, time.Now().Format(time.RFC3339))
	log.Printf("[HISTORY] [server] [scan_%s] command_creation_started agent_id=%s timestamp=%s",
		subsystem, agentID, time.Now().Format(time.RFC3339))

	command, err := h.commandFactory.CreateWithIdempotency(
		agentID,
		commandType,
		map[string]interface{}{"subsystem": subsystem},
		idempotencyKey,
	)
	if err != nil {
		log.Printf("[ERROR] [server] [scan_%s] command_creation_failed agent_id=%s error=%v", subsystem, agentID, err)
		log.Printf("[HISTORY] [server] [scan_%s] command_creation_failed error=\"%v\" timestamp=%s",
			subsystem, err, time.Now().Format(time.RFC3339))

		c.JSON(http.StatusInternalServerError, gin.H{
			"error": fmt.Sprintf("Failed to create %s scan command: %v", subsystem, err),
		})
		return
	}

	err = h.signAndCreateCommand(command)
	if err != nil {
		log.Printf("[ERROR] [server] [scan_%s] command_creation_failed agent_id=%s error=%v", subsystem, agentID, err)
		log.Printf("[HISTORY] [server] [scan_%s] command_creation_failed error=\"%v\" timestamp=%s",
			subsystem, err, time.Now().Format(time.RFC3339))

		c.JSON(http.StatusInternalServerError, gin.H{
			"error": fmt.Sprintf("Failed to create %s scan command: %v", subsystem, err),
		})
		return
	}

	log.Printf("[SUCCESS] [server] [scan_%s] command_created agent_id=%s command_id=%s timestamp=%s",
		subsystem, agentID, command.ID, time.Now().Format(time.RFC3339))
	log.Printf("[HISTORY] [server] [scan_%s] command_created agent_id=%s command_id=%s timestamp=%s",
		subsystem, agentID, command.ID, time.Now().Format(time.RFC3339))

	c.JSON(http.StatusOK, gin.H{
		"message":    "Subsystem scan triggered successfully",
		"command_id": command.ID,
	})
}

// GetSubsystemStats retrieves statistics for a subsystem
// GET /api/v1/agents/:id/subsystems/:subsystem/stats
func (h *SubsystemHandler) GetSubsystemStats(c *gin.Context) {
	agentID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
		return
	}

	subsystem := c.Param("subsystem")
	if subsystem == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Subsystem name required"})
		return
	}

	stats, err := h.subsystemQueries.GetSubsystemStats(agentID, subsystem)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve subsystem stats"})
		return
	}

	if stats == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Subsystem not found"})
		return
	}

	c.JSON(http.StatusOK, stats)
}

// SetAutoRun enables or disables auto-run for a subsystem
// POST /api/v1/agents/:id/subsystems/:subsystem/auto-run
func (h *SubsystemHandler) SetAutoRun(c *gin.Context) {
	agentID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
		return
	}

	subsystem := c.Param("subsystem")
	if subsystem == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Subsystem name required"})
		return
	}

	var req struct {
		AutoRun bool `json:"auto_run"`
	}

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

	err = h.subsystemQueries.SetAutoRun(agentID, subsystem, req.AutoRun)
	if err != nil {
		if err.Error() == "subsystem not found" {
			c.JSON(http.StatusNotFound, gin.H{"error": "Subsystem not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update auto-run"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Auto-run updated successfully"})
}

// SetInterval sets the interval for a subsystem
// POST /api/v1/agents/:id/subsystems/:subsystem/interval
func (h *SubsystemHandler) SetInterval(c *gin.Context) {
	agentID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid agent ID"})
		return
	}

	subsystem := c.Param("subsystem")
	if subsystem == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Subsystem name required"})
		return
	}

	var req struct {
		IntervalMinutes int `json:"interval_minutes"`
	}

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

	// Validate interval
	if req.IntervalMinutes < 5 || req.IntervalMinutes > 1440 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Interval must be between 5 and 1440 minutes"})
		return
	}

	err = h.subsystemQueries.SetInterval(agentID, subsystem, req.IntervalMinutes)
	if err != nil {
		if err.Error() == "subsystem not found" {
			c.JSON(http.StatusNotFound, gin.H{"error": "Subsystem not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update interval"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Interval updated successfully"})
}
