package forwarder

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"strings"

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

const AgentThreadAttestationHeader = "X-AgentTeam-Agent-Thread-Attestation"
const AgentThreadActingUserVerifiedJWTHeader = "X-AgentTeam-Acting-User-Verified-JWT"

// AgentThreadGateway adapts AgentTeam's agent-thread attestation boundary to the
// generic JSON forwarder. It only projects identity, workspace, and attestation
// headers; upstream services own request paths, bodies, and domain semantics.
type AgentThreadGateway struct {
	upstream *Handler
}

func NewAgentThreadGateway(deps Dependencies) (*AgentThreadGateway, error) {
	upstream, err := NewHandler(deps)
	if err != nil {
		return nil, err
	}
	return &AgentThreadGateway{upstream: upstream}, nil
}

func (gateway *AgentThreadGateway) ForwardAgentThreadUpstream(ctx context.Context, attestation agent.AgentThreadAttestation, method string, path string, request json.RawMessage) (json.RawMessage, error) {
	if gateway == nil || gateway.upstream == nil {
		return nil, agentThreadForwardError{kind: api.ErrBadRequest, message: "agent thread gateway is not configured"}
	}
	if strings.TrimSpace(attestation.WorkspaceID) == "" {
		return nil, agentThreadForwardError{kind: api.ErrBadRequest, message: "agent thread workspace is required"}
	}
	path, err := normalizeAgentThreadUpstreamPath(path)
	if err != nil {
		return nil, err
	}
	identity, err := agentThreadForwarderIdentity(attestation)
	if err != nil {
		return nil, err
	}
	encoded, err := EncodeAgentThreadAttestation(attestation)
	if err != nil {
		return nil, err
	}
	headers := map[string]string{
		AgentThreadAttestationHeader: encoded,
	}
	if actingUserIdentity, ok := agentThreadActingUserIdentity(attestation); ok {
		// When a human triggered this turn, use the acting-user identity as the
		// main upstream Identity so ForwardJSON mints the human's JWT into the
		// Authorization header. meshx HandleAuth reads Authorization (not the
		// X-AgentTeam-Acting-User-Verified-JWT header), so this makes meshx see the
		// human subject. The attestation header still carries the full agent
		// context. When ActingUserID is absent (non-human-triggered calls),
		// identity stays as the agent identity, preserving existing behavior.
		identity = actingUserIdentity
		actingUserJWT, err := gateway.upstream.issueTrustedJWTForIdentity(actingUserIdentity)
		if err != nil {
			return nil, err
		}
		headers[AgentThreadActingUserVerifiedJWTHeader] = "Bearer " + actingUserJWT
	}
	var response json.RawMessage
	err = gateway.upstream.ForwardJSON(ctx, JSONRequest{
		Method:      agentThreadForwardMethod(method),
		Path:        path,
		WorkspaceID: attestation.WorkspaceID,
		Identity:    identity,
		Body:        request,
		Headers:     headers,
	}, &response)
	return response, err
}

func agentThreadForwardMethod(method string) string {
	if method = strings.TrimSpace(method); method != "" {
		return method
	}
	return http.MethodPost
}

func normalizeAgentThreadUpstreamPath(path string) (string, error) {
	path = strings.TrimSpace(path)
	if path == "" {
		return "", agentThreadForwardError{kind: api.ErrBadRequest, message: "agent thread upstream path is required"}
	}
	if !strings.HasPrefix(path, "/") || strings.HasPrefix(path, "//") || strings.Contains(path, "..") {
		return "", agentThreadForwardError{kind: api.ErrBadRequest, message: "agent thread upstream path is invalid"}
	}
	return path, nil
}

func agentThreadForwarderIdentity(attestation agent.AgentThreadAttestation) (TrustedIdentity, error) {
	agentID := strings.TrimSpace(attestation.AgentID)
	if agentID == "" {
		return TrustedIdentity{}, agentThreadForwardError{kind: api.ErrForbidden, message: "agent thread attestation has no agent"}
	}
	agentMemberID := strings.TrimSpace(attestation.AgentMemberID)
	displayName := firstNonEmpty(agentMemberID, agentID)
	return TrustedIdentity{
		UserID:            agentID,
		AgentTeamUserID:        agentID,
		Name:              displayName,
		PreferredUsername: normalizePreferredUsernameCandidate(displayName),
		DisplayName:       displayName,
	}, nil
}

func agentThreadActingUserIdentity(attestation agent.AgentThreadAttestation) (TrustedIdentity, bool) {
	actingUserID := strings.TrimSpace(attestation.ActingUserID)
	if actingUserID == "" {
		return TrustedIdentity{}, false
	}
	displayName := firstNonEmpty(
		strings.TrimSpace(attestation.ActingDisplayName),
		strings.TrimSpace(attestation.ActingMemberID),
		actingUserID,
	)
	return TrustedIdentity{
		UserID:            actingUserID,
		AgentTeamUserID:        actingUserID,
		Name:              displayName,
		PreferredUsername: normalizePreferredUsernameCandidate(actingUserID),
		DisplayName:       displayName,
	}, true
}

type agentThreadAttestationPayload struct {
	WorkspaceID       string `json:"workspaceId"`
	ThreadChannelID   string `json:"threadChannelId"`
	ParentChannelID   string `json:"parentChannelId,omitempty"`
	RootMessageID     string `json:"rootMessageId,omitempty"`
	AgentID           string `json:"agentId"`
	AgentMemberID     string `json:"agentMemberId,omitempty"`
	ActingUserID      string `json:"actingUserId"`
	ActingMemberID    string `json:"actingMemberId,omitempty"`
	ActingDisplayName string `json:"actingDisplayName,omitempty"`
}

func EncodeAgentThreadAttestation(attestation agent.AgentThreadAttestation) (string, error) {
	payload, err := json.Marshal(agentThreadAttestationPayload{
		WorkspaceID:       strings.TrimSpace(attestation.WorkspaceID),
		ThreadChannelID:   strings.TrimSpace(attestation.ThreadChannelID),
		ParentChannelID:   strings.TrimSpace(attestation.ParentChannelID),
		RootMessageID:     strings.TrimSpace(attestation.RootMessageID),
		AgentID:           strings.TrimSpace(attestation.AgentID),
		AgentMemberID:     strings.TrimSpace(attestation.AgentMemberID),
		ActingUserID:      strings.TrimSpace(attestation.ActingUserID),
		ActingMemberID:    strings.TrimSpace(attestation.ActingMemberID),
		ActingDisplayName: strings.TrimSpace(attestation.ActingDisplayName),
	})
	if err != nil {
		return "", err
	}
	return base64.RawURLEncoding.EncodeToString(payload), nil
}

type agentThreadForwardError struct {
	kind    error
	message string
}

func (err agentThreadForwardError) Error() string { return err.message }

func (err agentThreadForwardError) Unwrap() error { return err.kind }
