package handlers

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"

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

// DownloadHandler handles agent binary downloads
type DownloadHandler struct {
	agentDir               string
	config                 *config.Config
	installTemplateService *services.InstallTemplateService
	packageQueries         *queries.PackageQueries
}

func NewDownloadHandler(agentDir string, cfg *config.Config, packageQueries *queries.PackageQueries) *DownloadHandler {
	return &DownloadHandler{
		agentDir:               agentDir,
		config:                 cfg,
		installTemplateService: services.NewInstallTemplateService(),
		packageQueries:         packageQueries,
	}
}

// getServerURL determines the server URL with proper protocol detection
func (h *DownloadHandler) getServerURL(c *gin.Context) string {
	// Priority 1: Use configured public URL if set
	if h.config.Server.PublicURL != "" {
		return h.config.Server.PublicURL
	}

	// Priority 2: Construct API server URL from configuration
	scheme := "http"
	host := h.config.Server.Host
	port := h.config.Server.Port

	// Use HTTPS if TLS is enabled in config
	if h.config.Server.TLS.Enabled {
		scheme = "https"
	}

	// For default host (0.0.0.0), use localhost for client connections
	if host == "0.0.0.0" {
		host = "localhost"
	}

	// Only include port if it's not the default for the protocol
	if (scheme == "http" && port != 80) || (scheme == "https" && port != 443) {
		return fmt.Sprintf("%s://%s:%d", scheme, host, port)
	}

	return fmt.Sprintf("%s://%s", scheme, host)
}

// DownloadAgent serves agent binaries for different platforms
func (h *DownloadHandler) DownloadAgent(c *gin.Context) {
	platform := c.Param("platform")
	version := c.Query("version") // Optional version parameter for signed binaries

	// Validate platform to prevent directory traversal
	validPlatforms := map[string]bool{
		"linux-amd64":   true,
		"linux-arm64":   true,
		"windows-amd64": true,
		"windows-arm64": true,
	}

	if !validPlatforms[platform] {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid or unsupported platform"})
		return
	}

	// Build filename based on platform
	filename := "redflag-agent"
	if strings.HasPrefix(platform, "windows") {
		filename += ".exe"
	}

	var agentPath string

	// Try to serve signed package first if version is specified (F-E1-1 fix)
	if version != "" {
		parts := strings.SplitN(platform, "-", 2)
		if len(parts) == 2 {
			signedPackage, err := h.packageQueries.GetSignedPackage(version, parts[0], parts[1])
			if err == nil && signedPackage != nil {
				// Path traversal guard for signed packages
				absSignedPath, absErr := filepath.Abs(signedPackage.BinaryPath)
				allowedBase, _ := filepath.Abs(h.config.BinaryStoragePath)
				if allowedBase == "" {
					allowedBase, _ = filepath.Abs("./binaries")
				}
				if absErr == nil && (strings.HasPrefix(absSignedPath, allowedBase+string(filepath.Separator)) || absSignedPath == allowedBase) {
					if _, statErr := os.Stat(absSignedPath); statErr == nil {
						agentPath = absSignedPath
						log.Printf("[INFO] [server] [downloads] serving_signed_package version=%s platform=%s", version, platform)
					}
				} else if absErr == nil {
					log.Printf("[ERROR] [server] [downloads] path_traversal_attempt_signed path=%s allowed=%s", absSignedPath, allowedBase)
				}
			}
		}
	}

	// Fallback to unsigned generic binary
	if agentPath == "" {
		agentPath = filepath.Join(h.agentDir, "binaries", platform, filename)
	}

	// Check if file exists and is not empty
	info, err := os.Stat(agentPath)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{
			"error":    "Agent binary not found",
			"platform": platform,
			"version":  version,
		})
		return
	}
	if info.Size() == 0 {
		c.JSON(http.StatusNotFound, gin.H{
			"error":    "Agent binary not found (empty file)",
			"platform": platform,
			"version":  version,
		})
		return
	}

	// Compute SHA256 checksum and set headers
	if checksum, err := computeFileSHA256(agentPath); err == nil {
		c.Header("X-Content-SHA256", checksum)
	}
	c.Header("X-Content-Length", strconv.FormatInt(info.Size(), 10))

	// Handle both GET and HEAD requests
	if c.Request.Method == "HEAD" {
		c.Status(http.StatusOK)
		return
	}

	c.File(agentPath)
}

// DownloadUpdatePackage serves signed agent update packages
func (h *DownloadHandler) DownloadUpdatePackage(c *gin.Context) {
	packageID := c.Param("package_id")

	// Validate package ID format (UUID)
	if len(packageID) != 36 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid package ID format"})
		return
	}

	parsedPackageID, err := uuid.Parse(packageID)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid package ID format"})
		return
	}

	// Fetch package from database
	pkg, err := h.packageQueries.GetSignedPackageByID(parsedPackageID)
	if err != nil {
		if err.Error() == "update package not found" {
			c.JSON(http.StatusNotFound, gin.H{
				"error":      "Package not found",
				"package_id": packageID,
			})
			return
		}

		log.Printf("[ERROR] [server] [downloads] package_fetch_failed package_id=%s error=%v", packageID, err)
		c.JSON(http.StatusInternalServerError, gin.H{
			"error":      "Failed to retrieve package",
			"package_id": packageID,
		})
		return
	}

	// Check if binary_path is populated
	if pkg.BinaryPath == "" {
		log.Printf("[WARNING] [server] [downloads] package_binary_path_empty package_id=%s", packageID)
		c.JSON(http.StatusNotImplemented, gin.H{
			"error":      "Package binary not yet available",
			"package_id": packageID,
		})
		return
	}

	// Resolve and sanitize binary path (defense in depth — prevent path traversal)
	absPath, err := filepath.Abs(pkg.BinaryPath)
	if err != nil {
		log.Printf("[ERROR] [server] [downloads] path_resolve_failed package_id=%s path=%s error=%v", packageID, pkg.BinaryPath, err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid package path"})
		return
	}

	allowedDir, _ := filepath.Abs(h.config.BinaryStoragePath)
	if allowedDir == "" {
		allowedDir, _ = filepath.Abs("./binaries")
	}
	if !strings.HasPrefix(absPath, allowedDir+string(filepath.Separator)) && absPath != allowedDir {
		log.Printf("[ERROR] [server] [downloads] path_traversal_attempt package_id=%s resolved_path=%s allowed_dir=%s", packageID, absPath, allowedDir)
		c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"})
		return
	}

	// Verify file exists on disk
	if _, err := os.Stat(absPath); os.IsNotExist(err) {
		log.Printf("[ERROR] [server] [downloads] package_file_not_found package_id=%s path=%s", packageID, absPath)
		c.JSON(http.StatusNotFound, gin.H{
			"error":      "Package file not found on disk",
			"package_id": packageID,
		})
		return
	}

	// Set appropriate headers
	c.Header("Content-Type", "application/octet-stream")
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filepath.Base(absPath)))
	c.Header("X-Package-Version", pkg.Version)
	c.Header("X-Package-Platform", pkg.Platform)
	c.Header("X-Package-Architecture", pkg.Architecture)

	if pkg.Signature != "" {
		c.Header("X-Package-Signature", pkg.Signature)
	}

	if pkg.Checksum != "" {
		c.Header("X-Package-Checksum", pkg.Checksum)
	}

	// Serve the file
	log.Printf("[INFO] [server] [downloads] package_download_served package_id=%s version=%s platform=%s", packageID, pkg.Version, pkg.Platform)
	c.File(absPath)
}

// InstallScript serves the installation script
func (h *DownloadHandler) InstallScript(c *gin.Context) {
	platform := c.Param("platform")

	// Validate platform
	validPlatforms := map[string]bool{
		"linux":   true,
		"windows": true,
	}

	if !validPlatforms[platform] {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid or unsupported platform"})
		return
	}

	serverURL := h.getServerURL(c)
	scriptContent := h.generateInstallScript(c, platform, serverURL)
	c.Header("Content-Type", "text/plain")
	c.String(http.StatusOK, scriptContent)
}

// parseAgentID extracts agent ID from header → path → query with security priority
func parseAgentID(c *gin.Context) string {
	// 1. Header → Secure (preferred)
	if agentID := c.GetHeader("X-Agent-ID"); agentID != "" {
		if _, err := uuid.Parse(agentID); err == nil {
			log.Printf("[DEBUG] Parsed agent ID from header: %s", agentID)
			return agentID
		}
		log.Printf("[DEBUG] Invalid UUID in header: %s", agentID)
	}

	// 2. Path parameter → Legacy compatible
	if agentID := c.Param("agent_id"); agentID != "" {
		if _, err := uuid.Parse(agentID); err == nil {
			log.Printf("[DEBUG] Parsed agent ID from path: %s", agentID)
			return agentID
		}
		log.Printf("[DEBUG] Invalid UUID in path: %s", agentID)
	}

	// 3. Query parameter → Fallback
	if agentID := c.Query("agent_id"); agentID != "" {
		if _, err := uuid.Parse(agentID); err == nil {
			log.Printf("[DEBUG] Parsed agent ID from query: %s", agentID)
			return agentID
		}
		log.Printf("[DEBUG] Invalid UUID in query: %s", agentID)
	}

	// Return placeholder for fresh installs
	log.Printf("[DEBUG] No valid agent ID found, using placeholder")
	return "<AGENT_ID>"
}

// HandleConfigDownload serves agent configuration templates with updated schema
// The install script injects the agent's actual credentials locally after download
func (h *DownloadHandler) HandleConfigDownload(c *gin.Context) {
	agentIDParam := c.Param("agent_id")

	// Validate UUID format
	parsedAgentID, err := uuid.Parse(agentIDParam)
	if err != nil {
		log.Printf("Invalid agent ID format for config download: %s, error: %v", agentIDParam, err)
		c.JSON(http.StatusBadRequest, gin.H{
			"error": "Invalid agent ID format",
		})
		return
	}

	// Log for security monitoring
	log.Printf("Config template download requested - agent_id: %s, remote_addr: %s",
		parsedAgentID.String(), c.ClientIP())

	// Get server URL for config
	serverURL := h.getServerURL(c)

	// Build config template with schema only (no sensitive credentials)
	// Credentials are preserved locally by the install script
	configTemplate := map[string]interface{}{
		"version":       5,      // Current schema version (v5 as of 0.1.23+)
		"agent_version": "0.2.0",
		"server_url":    serverURL,

		// Placeholder credentials - will be replaced by install script
		"agent_id":           "00000000-0000-0000-0000-000000000000",
		"token":              "",
		"refresh_token":      "",
		"registration_token": "",
		"machine_id":         "",

		// Standard configuration with all subsystems
		"check_in_interval":      300,
		"rapid_polling_enabled":  false,
		"rapid_polling_until":    "0001-01-01T00:00:00Z",

		"network": map[string]interface{}{
			"timeout":       30000000000,
			"retry_count":   3,
			"retry_delay":   5000000000,
			"max_idle_conn": 10,
		},

		"proxy": map[string]interface{}{
			"enabled": false,
		},

		"tls": map[string]interface{}{
			"enabled":              false,
			"insecure_skip_verify": false,
		},

		"logging": map[string]interface{}{
			"level":       "info",
			"max_size":    100,
			"max_backups": 3,
			"max_age":     28,
		},

		"subsystems": map[string]interface{}{
			"system": map[string]interface{}{
				"enabled": true,
				"timeout": 10000000000,
				"circuit_breaker": map[string]interface{}{
					"enabled":            true,
					"failure_threshold":  3,
					"failure_window":     600000000000,
					"open_duration":      1800000000000,
					"half_open_attempts": 2,
				},
			},
			"filesystem": map[string]interface{}{
				"enabled": true,
				"timeout": 10000000000,
				"circuit_breaker": map[string]interface{}{
					"enabled":            true,
					"failure_threshold":  3,
					"failure_window":     600000000000,
					"open_duration":      1800000000000,
					"half_open_attempts": 2,
				},
			},
			"network": map[string]interface{}{
				"enabled": true,
				"timeout": 30000000000,
				"circuit_breaker": map[string]interface{}{
					"enabled":            true,
					"failure_threshold":  3,
					"failure_window":     600000000000,
					"open_duration":      1800000000000,
					"half_open_attempts": 2,
				},
			},
			"processes": map[string]interface{}{
				"enabled": true,
				"timeout": 30000000000,
				"circuit_breaker": map[string]interface{}{
					"enabled":            true,
					"failure_threshold":  3,
					"failure_window":     600000000000,
					"open_duration":      1800000000000,
					"half_open_attempts": 2,
				},
			},
			"updates": map[string]interface{}{
				"enabled": true,
				"timeout": 30000000000,
				"circuit_breaker": map[string]interface{}{
					"enabled":            false,
					"failure_threshold":  0,
					"failure_window":     0,
					"open_duration":      0,
					"half_open_attempts": 0,
				},
			},
			"storage": map[string]interface{}{
				"enabled": true,
				"timeout": 10000000000,
				"circuit_breaker": map[string]interface{}{
					"enabled":            true,
					"failure_threshold":  3,
					"failure_window":     600000000000,
					"open_duration":      1800000000000,
					"half_open_attempts": 2,
				},
			},
		},

		"security": map[string]interface{}{
			"ed25519_verification": true,
			"nonce_validation":     true,
			"machine_id_binding":   true,
		},
	}

	// Return config template as JSON
	c.Header("Content-Type", "application/json")
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"redflag-config.json\""))
	c.JSON(http.StatusOK, configTemplate)
}

func (h *DownloadHandler) generateInstallScript(c *gin.Context, platform, baseURL string) string {
	// Parse agent ID with defense-in-depth priority
	agentIDParam := parseAgentID(c)

	// Extract registration token from query parameters
	registrationToken := c.Query("token")
	if registrationToken == "" {
		return "# Error: registration token is required\n# Please include token in URL: ?token=YOUR_TOKEN\n"
	}

	// Determine architecture: use ?arch= query param or default to amd64
	arch := c.DefaultQuery("arch", "amd64")
	validArchs := map[string]bool{"amd64": true, "arm64": true, "armv7": true}
	if !validArchs[arch] {
		arch = "amd64"
	}

	// Use template service to generate install scripts
	// Pass actual agent ID for upgrades, fallback placeholder for fresh installs
	script, err := h.installTemplateService.RenderInstallScriptFromBuild(
		agentIDParam,                                    // Real agent ID or placeholder
		platform,                                        // Platform (linux/windows)
		arch,                                            // Architecture
		"latest",                                        // Version
		baseURL,                                         // Server base URL
		registrationToken,                               // Registration token from query param
	)
	if err != nil {
		return fmt.Sprintf("# Error generating install script: %v", err)
	}
	return script
}

// computeFileSHA256 returns the lowercase hex-encoded SHA256 hash of a file.
func computeFileSHA256(path string) (string, error) {
	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()

	h := sha256.New()
	if _, err := io.Copy(h, f); err != nil {
		return "", err
	}
	return hex.EncodeToString(h.Sum(nil)), nil
}

