// Package api contains shared helpers for API domain handlers.
package api

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

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
	"github.com/tidbcloud/AgentTeam/internal/workspace/owner"
)

// HeaderWorkspaceID is the browser app header that selects the current workspace.
const HeaderWorkspaceID = "X-Workspace-Id"

// HeaderRouteWorkspaceID is the edge routing hint used to select a workspace owner.
const HeaderRouteWorkspaceID = "X-AgentTeam-Route-Workspace-Id"

const (
	ctxKeyAuthenticatedUser  = "api.auth.authenticated_user"
	ctxKeyAuthenticatedAgent = "api.auth.authenticated_agent"
	ctxKeyCurrentWorkspace   = "api.auth.current_workspace"
)

const errorCodeEmailNotVerified = "email_not_verified"

// SessionAuthenticator validates web-app bearer access tokens.
type SessionAuthenticator interface {
	AuthenticateSession(ctx context.Context, token string) (identity.AuthenticatedUser, error)
}

// WorkspaceResolver validates whether a user can access a workspace.
type WorkspaceResolver interface {
	GetWorkspaceForUser(ctx context.Context, userID string, workspaceID string) (workspace.Summary, error)
}

type ownedWorkspaceResolver struct {
	next    WorkspaceResolver
	managed owner.ManagedWorkspaceSet
}

// NewOwnedWorkspaceResolver decorates current-workspace auth with execution-owner checks.
func NewOwnedWorkspaceResolver(next WorkspaceResolver, managed owner.ManagedWorkspaceSet) WorkspaceResolver {
	return ownedWorkspaceResolver{next: next, managed: managed}
}

func (resolver ownedWorkspaceResolver) GetWorkspaceForUser(ctx context.Context, userID string, workspaceID string) (workspace.Summary, error) {
	if resolver.next == nil {
		return workspace.Summary{}, ErrForbidden
	}
	summary, err := resolver.next.GetWorkspaceForUser(ctx, userID, workspaceID)
	if err != nil {
		return workspace.Summary{}, err
	}
	if err := RequireWorkspaceOwner(resolver.managed, summary.WorkspaceID); err != nil {
		return workspace.Summary{}, err
	}
	return summary, nil
}

// BearerToken extracts a bearer token from the Authorization header.
func BearerToken(ctx *gin.Context) (string, bool) {
	raw := strings.TrimSpace(ctx.GetHeader("Authorization"))
	if raw == "" {
		return "", false
	}
	parts := strings.Fields(raw)
	if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
		return "", false
	}
	token := strings.TrimSpace(parts[1])
	if token == "" {
		return "", false
	}
	return token, true
}

// ProxyToken extracts an LLM proxy token from bearer auth or provider-style
// API key headers used by Anthropic-compatible clients.
func ProxyToken(ctx *gin.Context) (string, bool) {
	if token, ok := BearerToken(ctx); ok {
		return token, true
	}
	for _, header := range []string{"x-api-key", "api-key"} {
		token := strings.TrimSpace(ctx.GetHeader(header))
		if token != "" {
			return token, true
		}
	}
	return "", false
}

// WorkspaceID returns the current web-app workspace header value.
func WorkspaceID(ctx *gin.Context) (string, bool) {
	workspaceID := strings.TrimSpace(ctx.GetHeader(HeaderWorkspaceID))
	if workspaceID == "" {
		return "", false
	}
	return workspaceID, true
}

// RequireAuthenticatedUser resolves the bearer token once and stores the
// canonical user/session tuple for downstream workspace-scoped handlers.
func RequireAuthenticatedUser(auth SessionAuthenticator) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		if auth == nil {
			Error(ctx, http.StatusUnauthorized, "Invalid access token")
			ctx.Abort()
			return
		}
		token, ok := BearerToken(ctx)
		if !ok {
			Error(ctx, http.StatusUnauthorized, "Missing bearer token")
			ctx.Abort()
			return
		}
		authenticated, err := auth.AuthenticateSession(ctx.Request.Context(), token)
		if err != nil {
			Error(ctx, http.StatusUnauthorized, "Invalid access token")
			ctx.Abort()
			return
		}
		SetAuthenticatedUser(ctx, authenticated)
		ctx.Next()
	}
}

// RequireCurrentWorkspace validates X-Workspace-Id against the authenticated user and
// stores the workspace summary for downstream workspace-scoped handlers.
func RequireCurrentWorkspace(resolver WorkspaceResolver) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		if resolver == nil {
			Error(ctx, http.StatusForbidden, "Forbidden")
			ctx.Abort()
			return
		}
		workspaceID, ok := WorkspaceID(ctx)
		if !ok {
			Error(ctx, http.StatusBadRequest, "No workspace selected")
			ctx.Abort()
			return
		}

		authenticated, ok := AuthenticatedUserFromContext(ctx)
		if !ok {
			Error(ctx, http.StatusUnauthorized, "Unauthorized")
			ctx.Abort()
			return
		}

		workspaceSummary, err := resolver.GetWorkspaceForUser(ctx.Request.Context(), authenticated.User.ID, workspaceID)
		if err != nil {
			if errors.Is(err, owner.ErrOwnerMismatch) {
				ErrorWorkspaceOwnerMismatch(ctx)
				ctx.Abort()
				return
			}
			Error(ctx, http.StatusForbidden, "Forbidden")
			ctx.Abort()
			return
		}
		SetCurrentWorkspace(ctx, workspaceSummary)
		ctx.Next()
	}
}

// RequireRouteWorkspace resolves :workspaceId from the URL path and cross-validates
// against the X-Workspace-Id header if present. It stores the resolved workspace
// summary for downstream workspace-scoped handlers.
func RequireRouteWorkspace(resolver WorkspaceResolver) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		if resolver == nil {
			Error(ctx, http.StatusForbidden, "Forbidden")
			ctx.Abort()
			return
		}
		routeWorkspaceID, validRouteIdentity := CanonicalRouteWorkspaceID(ctx)
		if !validRouteIdentity {
			Error(ctx, http.StatusBadRequest, "workspaceId is invalid")
			ctx.Abort()
			return
		}
		authenticated, ok := AuthenticatedUserFromContext(ctx)
		if !ok {
			Error(ctx, http.StatusUnauthorized, "Unauthorized")
			ctx.Abort()
			return
		}
		workspaceSummary, err := resolver.GetWorkspaceForUser(ctx.Request.Context(), authenticated.User.ID, routeWorkspaceID)
		if err != nil {
			if errors.Is(err, owner.ErrOwnerMismatch) {
				ErrorWorkspaceOwnerMismatch(ctx)
				ctx.Abort()
				return
			}
			Error(ctx, http.StatusForbidden, "Forbidden")
			ctx.Abort()
			return
		}
		if strings.TrimSpace(workspaceSummary.WorkspaceID) != routeWorkspaceID {
			Error(ctx, http.StatusBadRequest, "resolved workspace does not match route workspace")
			ctx.Abort()
			return
		}
		SetCurrentWorkspace(ctx, workspaceSummary)
		ctx.Next()
	}
}

// CanonicalRouteWorkspaceID returns the validated workspace path identity when
// the path segment is unescaped and every optional public/backend workspace
// header agrees with it.
func CanonicalRouteWorkspaceID(ctx *gin.Context) (string, bool) {
	if ctx == nil {
		return "", false
	}
	workspaceID := strings.TrimSpace(ctx.Param("workspaceId"))
	if !IsValidRouteWorkspaceID(workspaceID) || !routeWorkspacePathSegmentIsWellFormed(ctx, workspaceID) {
		return "", false
	}
	for _, header := range []string{HeaderRouteWorkspaceID, HeaderWorkspaceID} {
		if headerWorkspaceID := strings.TrimSpace(ctx.GetHeader(header)); headerWorkspaceID != "" && headerWorkspaceID != workspaceID {
			return "", false
		}
	}
	return workspaceID, true
}

// IsValidRouteWorkspaceID reports whether a workspace ID is safe to use as one
// unescaped URL path segment. Existing workspace IDs use ASCII letters,
// numbers, underscores, and hyphens; keeping the validation shape-based avoids
// coupling routing to one historical ID prefix.
func IsValidRouteWorkspaceID(workspaceID string) bool {
	return identifiers.ValidWorkspaceID(workspaceID)
}

func routeWorkspacePathSegmentIsWellFormed(ctx *gin.Context, workspaceID string) bool {
	if ctx == nil || ctx.Request == nil || ctx.Request.URL == nil {
		return false
	}
	const prefix = "/api/v1/workspaces/"
	escapedPath := ctx.Request.URL.EscapedPath()
	if !strings.HasPrefix(escapedPath, prefix) {
		return false
	}
	segment := strings.SplitN(strings.TrimPrefix(escapedPath, prefix), "/", 2)[0]
	return segment == workspaceID
}

// RequireVerifiedIdentity keeps the legacy email/mobile verification gate as
// middleware so handler bodies remain focused on their route-specific work.
func RequireVerifiedIdentity(emailVerificationRequired bool) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		authenticated, ok := AuthenticatedUserFromContext(ctx)
		if !ok {
			Error(ctx, http.StatusUnauthorized, "Unauthorized")
			ctx.Abort()
			return
		}

		if !emailVerificationRequired || authenticated.User.HasVerifiedIdentity() {
			ctx.Next()
			return
		}

		ErrorWithCode(ctx, http.StatusForbidden, "Email or mobile not verified", errorCodeEmailNotVerified)
		ctx.Abort()
	}
}

// RequirePlatformOperator allows only platform operators to access global
// operations surfaces. Platform operators currently map to system-admin users.
func RequirePlatformOperator() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		authenticated, ok := AuthenticatedUserFromContext(ctx)
		if !ok {
			Error(ctx, http.StatusUnauthorized, "Unauthorized")
			ctx.Abort()
			return
		}
		if !authenticated.User.IsAdmin {
			Error(ctx, http.StatusForbidden, "Forbidden")
			ctx.Abort()
			return
		}
		ctx.Next()
	}
}

// SetAuthenticatedUser stores an authenticated user on the request context.
func SetAuthenticatedUser(ctx *gin.Context, authenticated identity.AuthenticatedUser) {
	ctx.Set(ctxKeyAuthenticatedUser, authenticated)
}

// SetAuthenticatedAgent stores the Agent principal established by Agent
// Runtime authentication middleware.
func SetAuthenticatedAgent(ctx *gin.Context, authenticated agent.AuthenticatedAgent) {
	ctx.Set(ctxKeyAuthenticatedAgent, authenticated)
}

// SetCurrentWorkspace stores the current workspace on the request context.
func SetCurrentWorkspace(ctx *gin.Context, workspaceSummary workspace.Summary) {
	ctx.Set(ctxKeyCurrentWorkspace, workspaceSummary)
}

// AuthenticatedUserFromContext returns the authenticated user stored by RequireAuthenticatedUser.
func AuthenticatedUserFromContext(ctx *gin.Context) (identity.AuthenticatedUser, bool) {
	value, ok := ctx.Get(ctxKeyAuthenticatedUser)
	if !ok {
		return identity.AuthenticatedUser{}, false
	}
	authenticated, ok := value.(identity.AuthenticatedUser)
	if !ok {
		return identity.AuthenticatedUser{}, false
	}
	return authenticated, true
}

// AuthenticatedAgentFromContext returns the trusted Agent principal established
// by Agent Runtime authentication middleware.
func AuthenticatedAgentFromContext(ctx *gin.Context) (agent.AuthenticatedAgent, bool) {
	if ctx == nil {
		return agent.AuthenticatedAgent{}, false
	}
	value, ok := ctx.Get(ctxKeyAuthenticatedAgent)
	if !ok {
		return agent.AuthenticatedAgent{}, false
	}
	authenticated, ok := value.(agent.AuthenticatedAgent)
	return authenticated, ok
}

// CurrentWorkspaceFromContext returns the workspace stored by RequireCurrentWorkspace.
func CurrentWorkspaceFromContext(ctx *gin.Context) (workspace.Summary, bool) {
	value, ok := ctx.Get(ctxKeyCurrentWorkspace)
	if !ok {
		return workspace.Summary{}, false
	}
	workspaceSummary, ok := value.(workspace.Summary)
	if !ok {
		return workspace.Summary{}, false
	}
	return workspaceSummary, true
}
