package machine

import (
	"errors"
	"log/slog"
	"mime"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/desktop/internal/api/auth"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/localsecurity"
	"github.com/tidbcloud/AgentTeam/desktop/internal/diagnosticbundle"
)

var diagnosticDownloadGate = make(chan struct{}, 1)

// DownloadDiagnostics streams one bounded diagnostic archive for the selected
// Workspace's current Desktop binding.
func (handler *Handler) DownloadDiagnostics(c *gin.Context) {
	target, ok := handler.activeTarget(c, strings.TrimSpace(c.Query("workspaceId")))
	if !ok {
		return
	}
	machineID := strings.TrimSpace(c.Param("machineId"))
	if machineID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "machineId is required"})
		return
	}
	localServer, ok := handler.service.currentBindingServer(target, machineID)
	if !ok {
		c.JSON(http.StatusConflict, gin.H{"error": "This Machine is not the current Desktop"})
		return
	}
	if strings.TrimSpace(handler.service.diagnosticPaths.ConfigDir) == "" {
		c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Desktop diagnostics are unavailable"})
		return
	}
	if !acquireDiagnosticDownload() {
		c.Header("Retry-After", "5")
		c.JSON(http.StatusTooManyRequests, gin.H{"error": "Another diagnostic archive is being collected"})
		return
	}
	defer releaseDiagnosticDownload()

	configuredAgents := handler.service.agentsForServer(localServer.ID)
	agents := make([]diagnosticbundle.Agent, 0, len(configuredAgents))
	for _, configured := range configuredAgents {
		agents = append(agents, diagnosticbundle.Agent{
			ID:                  configured.ID,
			Runtime:             configured.Runtime,
			RuntimeDebugEnabled: configured.RuntimeDebugEnabled,
		})
	}

	filename := "agentteam-diagnostics-" + time.Now().UTC().Format("20060102T150405Z") + ".zip"
	disposition := mime.FormatMediaType("attachment", map[string]string{"filename": filename})
	c.Header("Cache-Control", "no-store")
	c.Header("Content-Disposition", disposition)
	c.Header("Content-Type", "application/zip")
	c.Header("X-Content-Type-Options", "nosniff")
	c.Status(http.StatusOK)

	err := diagnosticbundle.Write(c.Request.Context(), c.Writer, handler.service.diagnosticPaths, diagnosticbundle.Scope{
		WorkspaceID: target.WorkspaceID,
		MachineID:   machineID,
		Agents:      agents,
	})
	if err != nil && !errors.Is(err, c.Request.Context().Err()) {
		slog.Warn("diagnostic archive streaming failed",
			"workspace_id", target.WorkspaceID,
			"machine_id", machineID,
			"error", err,
		)
	}
}

func diagnosticDownloadSession() gin.HandlerFunc {
	return func(c *gin.Context) {
		if err := localsecurity.ValidateQueryCredentials(c.Request); err != nil {
			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid query credentials"})
			return
		}
		sessionToken := strings.TrimSpace(c.Query("accessToken"))
		if sessionToken == "" {
			if authorization := c.GetHeader("Authorization"); authorization != "" {
				if tokenValue, ok := strings.CutPrefix(authorization, "Bearer "); ok {
					sessionToken = tokenValue
				}
			}
		}
		if sessionToken == "" {
			sessionToken = strings.TrimSpace(c.GetHeader("X-Session-Token"))
		}
		if !auth.AuthenticateSession(c, sessionToken) {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
			return
		}
		c.Next()
	}
}

func acquireDiagnosticDownload() bool {
	select {
	case diagnosticDownloadGate <- struct{}{}:
		return true
	default:
		return false
	}
}

func releaseDiagnosticDownload() {
	<-diagnosticDownloadGate
}
