// Package lookup owns the private workspace-route identity lookup
// adapter used by agentteam-gateway-server in split deployments.
package lookup

import (
	"context"
	"errors"
	"net/http"
	"net/url"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/internal/api"
)

const (
	maxIdentityFieldBytes = 16 << 10
	maxIdentityBodyBytes  = 80 << 10
)

// Resolver authenticates the supplied public request identity and returns one
// authoritative workspace. resolved=false is an expected fail-closed result.
type Resolver interface {
	ResolveWorkspaceRouteIdentity(context.Context, string, string, string, string, string, string, string) (workspaceID string, resolved bool, err error)
}

func bearerAuthorizationToken(authorization string) string {
	fields := strings.Fields(strings.TrimSpace(authorization))
	if len(fields) != 2 || !strings.EqualFold(fields[0], "Bearer") {
		return ""
	}
	return strings.TrimSpace(fields[1])
}

type Dependencies struct {
	Resolver           Resolver
	InternalAdminToken string
}

type resolveRequest struct {
	Method   string `json:"method"`
	Path     string `json:"path"`
	RawPath  string `json:"rawPath,omitempty"`
	RawQuery string `json:"rawQuery,omitempty"`
	// WorkspaceQualifiedSessionAuthorization is the original Edge-validated session
	// wire value. Keep its private JSON key stable and never log or echo it.
	WorkspaceQualifiedSessionAuthorization string `json:"authorization,omitempty"`
	Authority                              string `json:"authority,omitempty"`
	WorkspaceID                            string `json:"workspaceId,omitempty"`
}

// RegisterRoutes mounts the authenticated internal lookup on workspace/all
// profiles only. The public Gateway never receives MeshX or AGS database access.
func RegisterRoutes(router gin.IRouter, dependencies Dependencies) {
	if router == nil || dependencies.Resolver == nil {
		return
	}
	group := router.Group("/internal/meshxruntime", api.RequireInternalAdminCredential(dependencies.InternalAdminToken))
	group.POST("/workspace-route-identity", func(ctx *gin.Context) {
		ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, maxIdentityBodyBytes)
		var request resolveRequest
		if err := ctx.ShouldBindJSON(&request); err != nil || !validResolveRequest(request) {
			api.Error(ctx, http.StatusBadRequest, "invalid workspace route identity request")
			return
		}
		workspaceID, resolved, err := dependencies.Resolver.ResolveWorkspaceRouteIdentity(
			ctx.Request.Context(), request.Method, request.Path, request.RawPath, request.RawQuery, request.WorkspaceQualifiedSessionAuthorization,
			request.Authority, request.WorkspaceID,
		)
		if err != nil {
			if errors.Is(err, api.ErrUnauthorized) {
				api.ErrorWithCode(ctx, http.StatusUnauthorized, "Unauthorized", "workspace_identity_unauthorized")
				return
			}
			api.Error(ctx, http.StatusInternalServerError, "workspace route identity lookup failed")
			return
		}
		workspaceID = strings.TrimSpace(workspaceID)
		if !resolved || workspaceID == "" {
			api.ErrorWithCode(ctx, http.StatusUnprocessableEntity, "Workspace identity cannot be resolved", "workspace_identity_unresolved")
			return
		}
		ctx.JSON(http.StatusOK, gin.H{"workspaceId": workspaceID})
	})
}

func validResolveRequest(request resolveRequest) bool {
	method := strings.TrimSpace(request.Method)
	path := strings.TrimSpace(request.Path)
	if method == "" || path == "" || !strings.HasPrefix(path, "/") {
		return false
	}
	for _, value := range []string{method, path, request.RawPath, request.RawQuery, request.WorkspaceQualifiedSessionAuthorization, request.Authority} {
		if len(value) > maxIdentityFieldBytes {
			return false
		}
		for _, char := range value {
			if char == 0 || (char < ' ' && char != '\t') || char == 0x7f {
				return false
			}
		}
	}
	if request.RawPath != "" {
		decoded, err := url.PathUnescape(request.RawPath)
		if err != nil || decoded != request.Path {
			return false
		}
	}
	if request.Authority != "" {
		switch request.Authority {
		case "anonymous", "session_candidate", "repository_credential", "malformed":
		default:
			return false
		}
	}
	return true
}
