package agent

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

	"github.com/gin-gonic/gin"

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

// AgentProjectStore is the Agent-only project surface (list/create/delete),
// resolved from the authenticated agent principal. It mirrors the human role
// matrix with no relaxation and no fallback to the human Projects API.
type AgentProjectStore interface {
	ListProjectPageForAgent(ctx context.Context, authenticatedAgent storeagent.AuthenticatedAgent, options storeagent.ProjectListOptions) (storeagent.ProjectPage, error)
	CreateProjectForAgent(ctx context.Context, authenticatedAgent storeagent.AuthenticatedAgent, params storeagent.CreateProjectParams) (storeagent.Project, error)
	DeleteProjectForAgent(ctx context.Context, authenticatedAgent storeagent.AuthenticatedAgent, projectID string) error
}

// ProjectHandler exposes the Agent runtime project surface under
// /api/v1/workspaces/:workspaceId/agents/:agentId/runtime/projects.
type ProjectHandler struct {
	auth   AgentAuthenticator
	store  AgentProjectStore
	logger *slog.Logger
}

const (
	defaultAgentProjectListLimit = 20
	maxAgentProjectListLimit     = 100
)

type agentProjectListCursor struct {
	Version     int       `json:"v"`
	WorkspaceID string    `json:"workspaceId"`
	UpdatedAt   time.Time `json:"updatedAt"`
	ID          string    `json:"id"`
}

func parseAgentProjectListOptions(request api.AgentScopedRequest) (storeagent.ProjectListOptions, error) {
	limit := defaultAgentProjectListLimit
	if raw := strings.TrimSpace(request.Gin.Query("limit")); raw != "" {
		parsed, err := strconv.Atoi(raw)
		if err != nil || parsed < 1 || parsed > maxAgentProjectListLimit {
			return storeagent.ProjectListOptions{}, api.NewHTTPError(http.StatusBadRequest, "limit must be between 1 and 100")
		}
		limit = parsed
	}
	rawCursor := strings.TrimSpace(request.Gin.Query("cursor"))
	options := storeagent.ProjectListOptions{Limit: limit}
	if rawCursor == "" {
		return options, nil
	}
	var cursor agentProjectListCursor
	if err := decodeAgentCollectionCursor(rawCursor, "project", &cursor); err != nil || cursor.Version != 1 || cursor.WorkspaceID != request.Principal.WorkspaceID || cursor.UpdatedAt.IsZero() || strings.TrimSpace(cursor.ID) == "" {
		return storeagent.ProjectListOptions{}, api.NewHTTPError(http.StatusBadRequest, "invalid project cursor")
	}
	options.CursorUpdatedAt = cursor.UpdatedAt.UTC()
	options.CursorID = strings.TrimSpace(cursor.ID)
	return options, nil
}

// List handles GET .../runtime/projects. Returns projects the agent's workspace
// machine owner's user can see — owner ∪ granted ∪ workspace-visibility —
// same visibility as the human API. The page is ordered by updatedAt DESC, id
// ASC and is advanced by an opaque keyset cursor.
func (h *ProjectHandler) List(request api.AgentScopedRequest) (api.Response, error) {
	options, err := parseAgentProjectListOptions(request)
	if err != nil {
		return api.Response{}, err
	}
	page, err := h.store.ListProjectPageForAgent(request.Context(), request.Principal, options)
	if err != nil {
		return api.Response{}, err
	}
	nextCursor := ""
	if page.HasMore && len(page.Projects) > 0 {
		last := page.Projects[len(page.Projects)-1]
		nextCursor, err = encodeAgentCollectionCursor(agentProjectListCursor{Version: 1, WorkspaceID: request.Principal.WorkspaceID, UpdatedAt: last.UpdatedAt.UTC(), ID: last.ID})
		if err != nil {
			return api.Response{}, err
		}
	}
	return api.OK(gin.H{
		"projects":   converter.ToProjects(page.Projects),
		"hasMore":    page.HasMore,
		"nextCursor": nextCursor,
	}), nil
}

// Create handles POST .../runtime/projects. machineId is injected from the
// authenticated agent principal (server-side) and is never taken from the
// request body; a principal without a machine cannot create a project. The
// agent never supplies ownership — the store derives the user from the trusted
// machine and uses that user's human workspace member as Project owner.
func (h *ProjectHandler) Create(request api.AgentScopedJSON[dto.AgentProjectCreateRequest]) (api.Response, error) {
	machineID := strings.TrimSpace(request.Principal.MachineID)
	if machineID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "agent principal has no machine; cannot create project")
	}
	if strings.TrimSpace(request.Body.RootPath) == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "rootPath is required")
	}
	project, err := h.store.CreateProjectForAgent(request.Context(), request.Principal, storeagent.CreateProjectParams{
		WorkspaceID: request.Principal.WorkspaceID,
		MachineID:   machineID,
		RootPath:    request.Body.RootPath,
		Name:        request.Body.Name,
		Description: request.Body.Description,
		Visibility:  request.Body.Visibility,
	})
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(gin.H{"project": converter.ToProject(project)}), nil
}

// Delete handles DELETE .../runtime/projects/:projectId. Owner-only at the
// store layer, resolved from the trusted machine owner's user.
func (h *ProjectHandler) Delete(request api.AgentScopedRequest) (api.Response, error) {
	projectID := strings.TrimSpace(request.Gin.Param("projectId"))
	if projectID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "projectId is required")
	}
	if err := h.store.DeleteProjectForAgent(request.Context(), request.Principal, projectID); err != nil {
		return api.Response{}, err
	}
	return api.OK(gin.H{"deleted": true}), nil
}
