package agent

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

	"github.com/gin-gonic/gin"

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

type agentThreadAttestationRequest struct {
	ThreadChannelID string `json:"thread_channel_id"`
	TargetKey       string `json:"target_key"`
	TargetType      string `json:"target_type"`
	MessageID       string `json:"message_id"`
}

func (request agentThreadAttestationRequest) target() AgentThreadTarget {
	targetKey := strings.TrimSpace(request.TargetKey)
	if targetKey == "" {
		targetKey = strings.TrimSpace(request.ThreadChannelID)
	}
	return AgentThreadTarget{
		TargetKey:  targetKey,
		TargetType: strings.TrimSpace(request.TargetType),
		MessageID:  strings.TrimSpace(request.MessageID),
	}
}

func (h *AgentThreadHandler) resolveAgentThreadAttestation(c *gin.Context, target AgentThreadTarget) (AgentThreadAttestation, bool) {
	authenticatedAgent, ok := authenticate(c, h.auth)
	if !ok {
		return AgentThreadAttestation{}, false
	}
	if strings.TrimSpace(target.TargetKey) == "" && strings.TrimSpace(target.MessageID) == "" {
		err := badRequest("target_key or message_id is required")
		writeAgentThreadError(c, agentThreadStageRequestValidation, err)
		return AgentThreadAttestation{}, false
	}
	if h.contexts == nil {
		writeAgentThreadErrorResponse(c, http.StatusServiceUnavailable, "target_scope_resolver_unavailable", "agent target context store is not configured", agentThreadStageTargetScopeResolution)
		return AgentThreadAttestation{}, false
	}
	attestation, err := h.contexts.ResolveAgentThreadAttestation(c.Request.Context(), authenticatedAgent, target)
	if err != nil {
		writeAgentThreadError(c, agentThreadStageTargetScopeResolution, err)
		return AgentThreadAttestation{}, false
	}
	attestationWorkspaceID := strings.TrimSpace(attestation.WorkspaceID)
	authenticatedWorkspaceID := strings.TrimSpace(authenticatedAgent.WorkspaceID)
	if attestationWorkspaceID == "" || attestationWorkspaceID != authenticatedWorkspaceID {
		err := requestError{kind: api.ErrForbidden, message: "agent thread attestation belongs to a different workspace"}
		writeAgentThreadError(c, agentThreadStageTargetScopeResolution, err)
		return AgentThreadAttestation{}, false
	}
	if strings.TrimSpace(attestation.AgentID) != "" && strings.TrimSpace(attestation.AgentID) != strings.TrimSpace(authenticatedAgent.AgentID) {
		err := requestError{kind: api.ErrForbidden, message: "agent thread attestation belongs to a different agent"}
		writeAgentThreadError(c, agentThreadStageTargetScopeResolution, err)
		return AgentThreadAttestation{}, false
	}
	if strings.TrimSpace(attestation.AgentMemberID) != "" && strings.TrimSpace(attestation.AgentMemberID) != strings.TrimSpace(authenticatedAgent.MemberID) {
		err := requestError{kind: api.ErrForbidden, message: "agent thread attestation belongs to a different agent member"}
		writeAgentThreadError(c, agentThreadStageTargetScopeResolution, err)
		return AgentThreadAttestation{}, false
	}
	return attestation, true
}

const (
	agentThreadStageRequestValidation     = "request_validation"
	agentThreadStageTargetScopeResolution = "target_scope_resolution"
	agentThreadStageMeshXUpstream         = "meshx_upstream"
)

type agentThreadErrorDetails struct {
	Stage string `json:"stage"`
}

func writeAgentThreadError(c *gin.Context, stage string, err error) {
	status := HTTPStatus(err)
	code := "agent_thread_request_failed"
	switch stage {
	case agentThreadStageRequestValidation:
		code = "request_invalid"
	case agentThreadStageTargetScopeResolution:
		switch {
		case errors.Is(err, api.ErrNotFound):
			code = "target_scope_not_found"
		case errors.Is(err, api.ErrForbidden):
			code = "target_scope_not_authorized"
		case errors.Is(err, api.ErrBadRequest):
			code = "target_scope_invalid"
		default:
			code = "target_scope_resolution_failed"
		}
	case agentThreadStageMeshXUpstream:
		switch {
		case errors.Is(err, api.ErrNotFound):
			code = "meshx_upstream_not_found"
		case errors.Is(err, api.ErrForbidden):
			code = "meshx_upstream_forbidden"
		case errors.Is(err, api.ErrUnauthorized):
			code = "meshx_upstream_unauthorized"
		case errors.Is(err, api.ErrBadRequest):
			code = "meshx_upstream_rejected"
		default:
			code = "meshx_upstream_failed"
		}
	}
	writeAgentThreadErrorResponse(c, status, code, err.Error(), stage)
}

func writeAgentThreadErrorResponse(c *gin.Context, status int, code string, message string, stage string) {
	c.JSON(status, dto.AgentErrorResponse{
		Error:   strings.TrimSpace(message),
		Code:    strings.TrimSpace(code),
		Details: agentThreadErrorDetails{Stage: strings.TrimSpace(stage)},
	})
}
