package webagent

import (
	"log/slog"
	"net/http"
	"strconv"
	"strings"
	"time"

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

const maxAgentListLimit = 100

// List handles GET /api/v1/workspaces/:workspaceId/agents.
func (handler *CollectionHandler) List(r api.WorkspaceScopedRequest) (api.Response, error) {
	scope := strings.TrimSpace(strings.ToLower(r.Gin.Query("scope")))
	options, err := agentListOptionsFromQuery(r.Gin.Query("limit"), r.Gin.Query("includeRuntimeFailures"))
	if err != nil {
		return api.Response{}, err
	}
	switch scope {
	case "":
	case "owned":
		options.OwnedOnly = true
	default:
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "invalid agent scope")
	}
	agents, err := handler.agentStore.ListAgentsForWorkspaceWithOptions(r.Context(), r.UserID(), r.WorkspaceID(), options)
	if err != nil {
		return api.Response{}, err
	}
	api.OverlayAppAgentsRuntime(agents, handler.runtime, time.Now().UTC())
	return api.OK(converter.ToAppAgents(agents)), nil
}

func agentListOptionsFromQuery(rawLimit string, rawIncludeRuntimeFailures string) (agent.AppAgentListOptions, error) {
	limit, err := parseAgentListLimit(rawLimit)
	if err != nil {
		return agent.AppAgentListOptions{}, err
	}
	includeRuntimeFailures, err := parseIncludeRuntimeFailures(rawIncludeRuntimeFailures)
	if err != nil {
		return agent.AppAgentListOptions{}, err
	}
	return agent.AppAgentListOptions{
		Limit:                  limit,
		IncludeRuntimeFailures: includeRuntimeFailures,
	}, nil
}

func parseAgentListLimit(raw string) (int, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return 0, nil
	}
	limit, err := strconv.Atoi(trimmed)
	if err != nil || limit <= 0 {
		return 0, api.NewHTTPError(http.StatusBadRequest, "limit must be a positive integer")
	}
	if limit > maxAgentListLimit {
		return maxAgentListLimit, nil
	}
	return limit, nil
}

func parseIncludeRuntimeFailures(raw string) (bool, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return false, nil
	}
	includeRuntimeFailures, err := strconv.ParseBool(trimmed)
	if err != nil {
		return false, api.NewHTTPError(http.StatusBadRequest, "includeRuntimeFailures must be a boolean")
	}
	return includeRuntimeFailures, nil
}

// Create handles POST /api/v1/workspaces/:workspaceId/agents.
// Creates a new agent on the specified machine.
func (handler *CollectionHandler) Create(r api.WorkspaceScopedJSON[dto.WebAgentCreateRequest]) (api.Response, error) {
	request := r.Body
	svc := NewAgentService(handler.agentStore, nil, handler.runtime, handler.realtime, handler.logger, nil, handler.orgs, handler.hostedAgents, nil, false, handler.modelProfiles, handler.memory)
	createdAgent, err := svc.CreateAgent(r.Context(), r.UserID(), r.WorkspaceID(), CreateAgentInput{
		Name:            request.Name,
		MachineID:       request.MachineID,
		Runtime:         request.Runtime,
		Model:           request.Model,
		ReasoningEffort: request.ReasoningEffort,
		Description:     request.Description,
		LLMMode:         request.LLMMode,
		BaseURL:         request.BaseURL,
		APIKey:          request.APIKey,
		Headers:         request.Headers,
		WireAPI:         request.WireAPI,
		ModelProfileID:  request.ModelProfileID,
		ChannelIDs:      request.ChannelIDs,
		TemplateLocale:  templateLocaleFromAcceptLanguage(r.Gin.GetHeader("Accept-Language")),
	})
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToAppAgent(createdAgent)), nil
}

// Patch handles PATCH /api/agents/:agentId.
// Updates agent configuration and restarts if runtime parameters changed.
func (handler *CollectionHandler) Patch(r api.WorkspaceScopedJSON[dto.WebAgentUpdateRequest]) (api.Response, error) {
	request := r.Body
	agentID := strings.TrimSpace(r.Gin.Param("agentId"))
	if agentID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "agentId is required")
	}
	svc := NewAgentService(handler.agentStore, nil, handler.runtime, handler.realtime, handler.logger, nil, handler.orgs, handler.hostedAgents, nil, false, handler.modelProfiles, nil)
	updatedAgent, err := svc.UpdateAgent(r.Context(), r.UserID(), r.WorkspaceID(), UpdateAgentInput{
		AgentID:         agentID,
		Name:            request.Name,
		DisplayName:     request.DisplayName,
		DeriveName:      request.DeriveName,
		Description:     request.Description,
		AvatarURL:       request.AvatarURL,
		Runtime:         request.Runtime,
		PermissionMode:  request.PermissionMode,
		Model:           request.Model,
		ReasoningEffort: request.ReasoningEffort,
		LLMMode:         request.LLMMode,
		BaseURL:         request.BaseURL,
		APIKey:          request.APIKey,
		Headers:         request.Headers,
		WireAPI:         request.WireAPI,
		ModelProfileID:  request.ModelProfileID,
	})
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToAppAgent(updatedAgent)), nil
}

func permissionModeTightensToWorkspaceWrite(currentMode string, nextMode string) bool {
	return agent.DefaultAgentPermissionMode(currentMode) != agent.AgentPermissionModeWorkspaceWrite &&
		agent.DefaultAgentPermissionMode(nextMode) == agent.AgentPermissionModeWorkspaceWrite
}

func agentRuntimeShouldRestart(runtime Runtime, agent agent.AgentView) bool {
	if agentRuntimeRunning(agent) {
		return true
	}
	return runtime.AgentRuntimeRunning(agent.MachineID, agent.AgentID)
}

func agentRuntimeRunning(agent agent.AgentView) bool {
	if strings.TrimSpace(agent.Status) == "active" {
		return true
	}
	switch strings.TrimSpace(agent.Activity) {
	case "", "offline", "sleeping":
		return false
	default:
		return true
	}
}

// Delete handles DELETE /api/agents/:agentId.
// Deletes an agent and garbage-collects its recovery points.
func (handler *CollectionHandler) Delete(r api.WorkspaceScopedRequest) (api.Response, error) {
	agentID := strings.TrimSpace(r.Gin.Param("agentId"))
	if agentID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "agentId is required")
	}
	svc := NewAgentService(handler.agentStore, handler.rewindStore, handler.runtime, handler.realtime, handler.logger, nil, nil, nil, nil, false, nil, nil)
	if err := svc.DeleteAgent(r.Context(), r.UserID(), r.WorkspaceID(), agentID); err != nil {
		return api.Response{}, err
	}
	return api.OK(dto.EmptyResponse{}), nil
}

func optionalStringValue(value dto.OptionalString) *string {
	if value.Value == nil {
		empty := ""
		return &empty
	}
	return value.Value
}

func stringValueFromOptionalString(value dto.OptionalString, fallback string) string {
	if !value.Set {
		return fallback
	}
	if value.Value == nil {
		return ""
	}
	return strings.TrimSpace(*value.Value)
}

func mapValueFromOptionalStringMap(value dto.OptionalStringMap, fallback map[string]string) map[string]string {
	if !value.Set {
		return copyStringMap(fallback)
	}
	return copyStringMap(value.Value)
}

func copyStringMap(values map[string]string) map[string]string {
	if len(values) == 0 {
		return map[string]string{}
	}
	copied := make(map[string]string, len(values))
	for key, value := range values {
		copied[key] = value
	}
	return copied
}

func stringPointer(value string) *string {
	return &value
}

func warn(logger *slog.Logger, message string, args ...any) {
	if logger == nil {
		return
	}
	logger.Warn(message, args...)
}

// NotifyMemoryAccess notifies the daemon that memory access for the given agent
// has changed. Called by the web UI after it mutates memory permissions in MeshX.
// The daemon re-evaluates its memory watcher state on receipt.
func (h *CollectionHandler) NotifyMemoryAccess(r api.WorkspaceScopedRequest) (api.Response, error) {
	agentID := strings.TrimSpace(r.Gin.Param("agentId"))
	if agentID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "agentId is required")
	}

	ag, err := h.agentStore.GetAgentForUser(r.Context(), r.UserID(), r.WorkspaceID(), agentID)
	if err != nil {
		return api.Response{}, api.NewHTTPError(http.StatusNotFound, "agent not found")
	}

	notifyRuntimeMemoryAccessChanged(r.Context(), h.runtime, h.logger, r.WorkspaceID(), agentID, ag.MachineID)

	return api.OK(dto.EmptyResponse{}), nil
}
