package agent

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

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	storeagent "github.com/tidbcloud/AgentTeam/internal/store/agent"
)

const LLMProxyCredentialKindMaaSCapability = "maas_capability"

var errMaaSLLMProxySessionsUnavailable = errors.New("maas llm proxy sessions are unavailable")

// LLMProxySessionStore issues agentteam-signed MaaS runtime capabilities.
type LLMProxySessionStore interface {
	IssueMaaSLLMProxySession(ctx context.Context, authenticated storeagent.AuthenticatedAgent, selectorSessionID string) (storeagent.LLMProxySession, error)
	RefreshMaaSLLMProxySession(ctx context.Context, authenticated storeagent.AuthenticatedAgent, selectorSessionID string) (storeagent.LLMProxySession, error)
}

// LLMProxySessionHandler owns runtime-scoped LLM proxy session routes.
type LLMProxySessionHandler struct {
	auth     AgentAuthenticator
	sessions LLMProxySessionStore
}

// Issue handles the canonical Agent Runtime LLM proxy session route.
func (h *LLMProxySessionHandler) Issue(c *gin.Context) {
	authenticated, ok := authenticate(c, h.auth)
	if !ok {
		return
	}
	if h.sessions == nil {
		c.JSON(http.StatusServiceUnavailable, agentError("llm proxy sessions are unavailable"))
		return
	}
	var request dto.AgentLLMProxySessionRequest
	if !bindOptionalJSON(c, &request) {
		return
	}
	session, err := h.issue(c.Request.Context(), authenticated, request)
	if err != nil {
		if errors.Is(err, errMaaSLLMProxySessionsUnavailable) {
			c.JSON(http.StatusServiceUnavailable, agentError(err.Error()))
			return
		}
		c.JSON(HTTPStatus(err), llmProxySessionError(err))
		return
	}
	c.JSON(http.StatusOK, llmProxySessionResponse(session))
}

// Refresh handles the canonical Agent Runtime LLM proxy session refresh route.
func (h *LLMProxySessionHandler) Refresh(c *gin.Context) {
	authenticated, ok := authenticate(c, h.auth)
	if !ok {
		return
	}
	if h.sessions == nil {
		c.JSON(http.StatusServiceUnavailable, agentError("llm proxy sessions are unavailable"))
		return
	}
	var request dto.AgentLLMProxySessionRequest
	if !bindOptionalJSON(c, &request) {
		return
	}
	session, err := h.refresh(c.Request.Context(), authenticated, request)
	if err != nil {
		if errors.Is(err, errMaaSLLMProxySessionsUnavailable) {
			c.JSON(http.StatusServiceUnavailable, agentError(err.Error()))
			return
		}
		c.JSON(HTTPStatus(err), llmProxySessionError(err))
		return
	}
	c.JSON(http.StatusOK, llmProxySessionResponse(session))
}

func (h *LLMProxySessionHandler) issue(ctx context.Context, authenticated storeagent.AuthenticatedAgent, request dto.AgentLLMProxySessionRequest) (storeagent.LLMProxySession, error) {
	credentialKind := strings.ToLower(strings.TrimSpace(request.CredentialKind))
	selectorSessionID := strings.TrimSpace(request.SessionID)
	switch credentialKind {
	case LLMProxyCredentialKindMaaSCapability:
		return h.sessions.IssueMaaSLLMProxySession(ctx, authenticated, selectorSessionID)
	default:
		return storeagent.LLMProxySession{}, badRequest("unsupported llm proxy credentialKind")
	}
}

func (h *LLMProxySessionHandler) refresh(ctx context.Context, authenticated storeagent.AuthenticatedAgent, request dto.AgentLLMProxySessionRequest) (storeagent.LLMProxySession, error) {
	credentialKind := strings.ToLower(strings.TrimSpace(request.CredentialKind))
	selectorSessionID := strings.TrimSpace(request.SessionID)
	switch credentialKind {
	case LLMProxyCredentialKindMaaSCapability:
		return h.sessions.RefreshMaaSLLMProxySession(ctx, authenticated, selectorSessionID)
	default:
		return storeagent.LLMProxySession{}, badRequest("unsupported llm proxy credentialKind")
	}
}

func llmProxySessionResponse(session storeagent.LLMProxySession) dto.AgentLLMProxySessionResponse {
	return dto.AgentLLMProxySessionResponse{
		ProxyToken:          session.ProxyToken,
		ExpiresAt:           session.ExpiresAt,
		SessionID:           session.SessionID,
		RefreshAt:           session.RefreshAt,
		SupersedesSessionID: session.SupersedesSessionID,
		MaasBaseURL:         session.MaasBaseURL,
		CredentialKind:      session.CredentialKind,
	}
}

func llmProxySessionError(err error) dto.AgentErrorResponse {
	var runtimeSessionNotReady maasRuntimeSessionNotReadyError
	if errors.As(err, &runtimeSessionNotReady) {
		return agentErrorWithCode(runtimeSessionNotReady.Error(), maasRuntimeSessionNotReadyCode)
	}
	return agentError(err.Error())
}

func bindOptionalJSON(c *gin.Context, request any) bool {
	if request == nil {
		return true
	}
	if err := c.ShouldBindJSON(request); err != nil {
		if errors.Is(err, io.EOF) {
			return true
		}
		c.JSON(http.StatusBadRequest, agentError("invalid request body"))
		return false
	}
	return true
}
