package webagent

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

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/runtimecontrol"
	"github.com/tidbcloud/AgentTeam/internal/service/agentruntime"
)

// Start handles POST /api/agents/:agentId/start.
// Enables auto-wake and starts the agent runtime.
func (handler *RuntimeHandler) Start(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")
	}

	if handler.runtimeCommands != nil {
		if _, err := handler.runtimeCommands.StartAgentRuntimeForUser(r.Context(), r.UserID(), r.WorkspaceID(), agentID); err != nil {
			return api.Response{}, err
		}
		return api.OK(dto.EmptyResponse{}), nil
	}

	agent, err := handler.agentStore.SetAgentAutoWakeForUser(r.Context(), r.UserID(), r.WorkspaceID(), agentID, true)
	if err != nil {
		return api.Response{}, err
	}

	if handler.runtime != nil {
		if err := handler.runtime.SyncMachineCatalog(r.Context(), agent.MachineID); err != nil && !errors.Is(err, runtimecontrol.ErrMachineOffline) {
			return api.Response{}, err
		}
		if err := handler.runtime.StartAgent(r.Context(), agent.MachineID, agent.AgentID); err != nil && !errors.Is(err, runtimecontrol.ErrMachineOffline) {
			return api.Response{}, err
		}
	}

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

// Stop handles POST /api/agents/:agentId/stop.
// Disables auto-wake and stops the agent runtime.
func (handler *RuntimeHandler) Stop(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")
	}

	if handler.runtimeCommands != nil {
		agent, err := handler.runtimeCommands.StopAgentRuntimeForUser(r.Context(), r.UserID(), r.WorkspaceID(), agentID)
		if err != nil {
			return api.Response{}, err
		}
		handler.emitAgentStoppingIfRunning(r.WorkspaceID(), r.UserID(), agent)
		return api.OK(dto.EmptyResponse{}), nil
	}

	agent, err := handler.agentStore.SetAgentAutoWakeForUser(r.Context(), r.UserID(), r.WorkspaceID(), agentID, false)
	if err != nil {
		return api.Response{}, err
	}

	wasRunning := shouldEmitAgentStopping(handler.runtime, agent)
	if handler.runtime != nil {
		if err := handler.runtime.StopAgent(agent.MachineID, agent.AgentID); err != nil && !errors.Is(err, runtimecontrol.ErrMachineOffline) {
			return api.Response{}, err
		}
	}
	if handler.realtime != nil && wasRunning {
		handler.realtime.EmitAgentActivity(r.WorkspaceID(), r.UserID(), agent.AgentID, "stopping", "")
	}

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

// Interrupt handles POST /api/agents/:agentId/interrupt.
// Interrupts the agent's current runtime turn while preserving its session and
// auto-wake state. Used to cancel an in-flight agent-edit without stopping it.
func (handler *RuntimeHandler) Interrupt(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")
	}

	agent, err := handler.agentStore.GetAgentWorkspaceReaderForUser(r.Context(), r.UserID(), r.WorkspaceID(), agentID)
	if err != nil {
		return api.Response{}, err
	}

	if handler.runtime != nil {
		if err := handler.runtime.InterruptAgentRun(agent.MachineID, agent.AgentID); err != nil {
			if errors.Is(err, runtimecontrol.ErrMachineOffline) {
				return api.Response{}, api.NewHTTPError(http.StatusServiceUnavailable, "Machine is offline")
			}
			return api.Response{}, err
		}
	}

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

// Reset handles POST /api/agents/:agentId/reset.
// Stops the agent, optionally clears its workspace, and resets its session.
func (handler *RuntimeHandler) Reset(r api.WorkspaceScopedJSON[dto.WebAgentResetRequest]) (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")
	}

	if request.ClearWorkspace && handler.runtime == nil {
		return api.Response{}, api.NewHTTPError(http.StatusServiceUnavailable, "Machine is offline")
	}

	if handler.runtimeCommands != nil {
		if err := handler.runtimeCommands.ResetAgentRuntimeForUser(r.Context(), r.UserID(), r.WorkspaceID(), agentID, request.ClearWorkspace); err != nil {
			if errors.Is(err, agentruntime.ErrRuntimeOffline) {
				return api.Response{}, api.NewHTTPError(http.StatusServiceUnavailable, "Machine is offline")
			}
			return api.Response{}, err
		}
		return api.OK(dto.EmptyResponse{}), nil
	}

	agent, err := handler.agentStore.GetAgentWorkspaceReaderForUser(r.Context(), r.UserID(), r.WorkspaceID(), agentID)
	if err != nil {
		return api.Response{}, err
	}

	if handler.runtime != nil {
		_ = handler.runtime.ResetAgentSession(agent.MachineID, agent.AgentID)
	}

	if handler.runtime != nil && request.ClearWorkspace {
		if err := handler.runtime.ResetAgentWorkspace(agent.MachineID, agent.AgentID); err != nil {
			if errors.Is(err, runtimecontrol.ErrMachineOffline) {
				return api.Response{}, api.NewHTTPError(http.StatusServiceUnavailable, "Machine is offline")
			}
			return api.Response{}, err
		}
	}

	if handler.rewindStore == nil {
		return api.Response{}, api.ErrNotFound
	}
	recoveryPoints, err := handler.rewindStore.DeleteAllAgentRecoveryPointsForWorkspace(r.Context(), r.UserID(), r.WorkspaceID(), agentID)
	if err != nil {
		return api.Response{}, err
	}

	if handler.runtime != nil && len(recoveryPoints) > 0 {
		if err := handler.runtime.GCRecoveryPoints(agent.MachineID, agent.AgentID, recoveryPoints); err != nil && !errors.Is(err, runtimecontrol.ErrMachineOffline) {
			warn(handler.logger, "gc reset agent recovery points", slog.String("agent_id", agent.AgentID), slog.Any("error", err))
		}
	}

	if err := handler.agentStore.ClearAgentSessionForWorkspace(r.Context(), r.UserID(), r.WorkspaceID(), agentID); err != nil {
		return api.Response{}, err
	}

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

func (handler *RuntimeHandler) organizationProvider() (OrganizationProvider, bool) {
	if handler.orgs == nil {
		return nil, false
	}
	return handler.orgs, true
}
