// Package tokenanalysis adapts the daemon-owned Token analysis service to HTTP.
package tokenanalysis

import (
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/desktop/internal/token"
	analysis "github.com/tidbcloud/AgentTeam/desktop/internal/tokenanalysis"
)

const RootPath = "/api/internal/token-analysis/v2"

type Dependencies struct {
	Service            *analysis.Service
	ValidateAgentToken func(agentID, value string) error
}

type analysisRequest struct {
	Window            string `form:"window"`
	AgentRef          string `form:"agentRef"`
	RunRef            string `form:"runRef"`
	Case              string `form:"case"`
	ChangedVariable   string `form:"changedVariable"`
	WorkspaceRevision string `form:"workspaceRevision"`
	ToolProfile       string `form:"toolProfile"`
	CacheState        string `form:"cacheState"`
	Outcome           string `form:"outcome"`
	Stop              bool   `form:"stop"`
}

func (request analysisRequest) analysisRequest() analysis.Request {
	return analysis.Request{
		Window:   request.Window,
		AgentRef: request.AgentRef,
		RunRef:   request.RunRef,
		Experiment: analysis.ExperimentMetadata{
			Case:            request.Case,
			ChangedVariable: request.ChangedVariable,
			Workspace:       request.WorkspaceRevision,
			Tools:           request.ToolProfile,
			CacheState:      request.CacheState,
			Outcome:         request.Outcome,
		},
	}
}

type collectionRequest struct {
	Enabled bool `json:"enabled"`
}

func RegisterHumanRoutes(group *gin.RouterGroup, dependencies Dependencies) {
	if dependencies.Service == nil {
		return
	}
	group.GET("/analysis", func(c *gin.Context) {
		var input analysisRequest
		if err := c.ShouldBindQuery(&input); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_request"})
			return
		}
		request := input.analysisRequest()
		var (
			report analysis.Report
			err    error
		)
		if c.Query("passive") == "true" {
			report, err = dependencies.Service.Analyze(request, true, false, "", false)
		} else {
			report, err = dependencies.Service.Analyze(request, true, input.Stop, "", true)
		}
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		c.JSON(http.StatusOK, report)
	})
	group.GET("/collection", func(c *gin.Context) {
		c.JSON(http.StatusOK, analysis.CollectionStatus{
			Enabled:          dependencies.Service.CollectionEnabled(),
			CallerCanControl: true,
		})
	})
	group.PUT("/collection", func(c *gin.Context) {
		var input collectionRequest
		reader := http.MaxBytesReader(c.Writer, c.Request.Body, 64<<10)
		defer reader.Close()
		decoder := json.NewDecoder(reader)
		decoder.DisallowUnknownFields()
		if err := decoder.Decode(&input); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_request"})
			return
		}
		var trailing any
		if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) {
			c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_request"})
			return
		}
		dependencies.Service.SetCollectionEnabled(input.Enabled)
		c.JSON(http.StatusOK, analysis.CollectionStatus{
			Enabled:          dependencies.Service.CollectionEnabled(),
			CallerCanControl: true,
		})
	})
}

// RegisterAgentRoutes uses an Agent-bound token and derives the only visible
// scope from that token. Request parameters can never widen it.
func RegisterAgentRoutes(group *gin.RouterGroup, dependencies Dependencies) {
	if dependencies.Service == nil {
		return
	}
	group.GET("/analysis", func(c *gin.Context) {
		agentID := strings.TrimSpace(c.Param("agentId"))
		tokenValue := strictBearer(c.GetHeader("Authorization"))
		if agentID == "" || tokenValue == "" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
			return
		}
		validate := dependencies.ValidateAgentToken
		if validate == nil {
			validate = func(agentID, value string) error {
				_, err := token.Default().ValidateTokenAnalysisAgentTokenInfo(agentID, value)
				return err
			}
		}
		if err := validate(agentID, tokenValue); err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
			return
		}
		var input analysisRequest
		if err := c.ShouldBindQuery(&input); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_request"})
			return
		}
		report, err := dependencies.Service.Analyze(input.analysisRequest(), false, false, agentID, false)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		c.JSON(http.StatusOK, report)
	})
}

func strictBearer(header string) string {
	const prefix = "Bearer "
	if !strings.HasPrefix(header, prefix) {
		return ""
	}
	value := strings.TrimSpace(strings.TrimPrefix(header, prefix))
	if value == "" || strings.ContainsAny(value, " \t\r\n") {
		return ""
	}
	return value
}
