package plan

import (
	"context"

	"github.com/gin-gonic/gin"

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

const invalidRequestBodyMessage = "invalid request body"

// Dependencies contains collaborators for Workflow Plan authoring routes.
type Dependencies struct {
	AuthStore                 api.SessionAuthenticator
	WorkspaceStore            api.WorkspaceResolver
	EmailVerificationRequired bool
	Service                   Service
	AgentEdit                 AgentEditService
	// DirectPublishGuard prevents Team-owned reusable Plans from bypassing
	// their Human approval and deletion lifecycle through the workspace-wide
	// authoring API. Read and draft-edit surfaces stay available, but only the
	// Team Workflow service may publish, archive, or restore a linked Plan.
	DirectPublishGuard DirectPublishGuard
}

type DirectPublishGuard interface {
	RequireUnlinkedPlan(context.Context, string, string) error
}

// PlanManagementResolver is an optional read-side capability implemented by
// the Team Workflow service. Primitive return values keep this authoring API
// independent of the Team Workflow service and store packages.
//
// linked=false means the Plan is managed directly by the workspace authoring
// API. A deleted Team relation remains linked because its tombstone continues
// to own the Plan lifecycle.
type PlanManagementResolver interface {
	ResolvePlanManagement(context.Context, string, string) (
		teamID string,
		teamWorkflowID string,
		deleted bool,
		linked bool,
		err error,
	)
}

func handleWorkspaceJSON[T any](handler func(api.WorkspaceScopedJSON[T]) (api.Response, error)) gin.HandlerFunc {
	return api.HandleWorkspaceScoped(func(request api.WorkspaceScopedRequest) (api.Response, error) {
		body, err := decodeWorkflowPlanJSON[T](request.Gin)
		if err != nil {
			return api.Response{}, err
		}
		return handler(api.WorkspaceScopedJSON[T]{WorkspaceScopedRequest: request, Body: body})
	}, api.WithErrorClassifier(classifyError))
}

func handleWorkspace(handler api.WorkspaceScopedHandler) gin.HandlerFunc {
	return api.HandleWorkspaceScoped(handler, api.WithErrorClassifier(classifyError))
}

// RegisterRoutes mounts the Workflow Plan authoring API.
func RegisterRoutes(router gin.IRouter, dependencies Dependencies) {
	if router == nil || dependencies.Service == nil {
		return
	}
	workspaceScoped := router.Group(
		"/api/v1/workspaces/:workspaceId",
		api.RequireAuthenticatedUser(dependencies.AuthStore),
		api.RequireVerifiedIdentity(dependencies.EmailVerificationRequired),
		api.RequireRouteWorkspace(dependencies.WorkspaceStore),
	)
	handlers := newRouteHandlers(dependencies.Service, dependencies.DirectPublishGuard)
	handlers.detail.agentEdit = dependencies.AgentEdit

	workspaceScoped.GET("/workflow/plans", handleWorkspace(handlers.collection.List))
	workspaceScoped.POST("/workflow/plans", handleWorkspaceJSON[dto.WorkflowPlanCreateRequest](handlers.collection.Create))
	workspaceScoped.POST("/workflow/plans:validate", handleWorkspaceJSON[dto.WorkflowPlanValidateRequest](handlers.validation.Validate))

	workspaceScoped.GET("/workflow/plans/:planId", handleWorkspace(handlers.detail.Get))
	workspaceScoped.PATCH("/workflow/plans/:planId", handleWorkspaceJSON[dto.WorkflowPlanUpdateRequest](handlers.detail.Update))
	workspaceScoped.POST("/workflow/plans/:planId/archive", handleWorkspace(handlers.detail.Archive))
	workspaceScoped.POST("/workflow/plans/:planId/restore", handleWorkspace(handlers.detail.Restore))

	workspaceScoped.GET("/workflow/plans/:planId/draft", handleWorkspace(handlers.draft.Get))
	workspaceScoped.PUT("/workflow/plans/:planId/draft", handleWorkspaceJSON[dto.WorkflowPlanDraftSaveRequest](handlers.draft.Save))
	workspaceScoped.POST("/workflow/plans/:planId/draft:validate", handleWorkspaceJSON[dto.WorkflowPlanDraftValidateRequest](handlers.validation.ValidateDraft))

	workspaceScoped.POST("/workflow/plans/:planId/versions", handleWorkspaceJSON[dto.WorkflowPlanPublishRequest](handlers.versions.Publish))
	workspaceScoped.GET("/workflow/plans/:planId/versions", handleWorkspace(handlers.versions.List))
	workspaceScoped.GET("/workflow/plans/:planId/versions/:versionId", handleWorkspace(handlers.versions.Get))
	workspaceScoped.POST("/workflow/plans/:planId/versions/:versionId/republish", handleWorkspaceJSON[dto.WorkflowPlanRepublishRequest](handlers.versions.Republish))
	workspaceScoped.POST("/workflow/plans/:planId/versions/:versionId/make-default", handleWorkspace(handlers.versions.MakeDefault))

	if dependencies.AgentEdit != nil {
		agentEdit := &AgentEditHandler{
			service:    agentEditAdapter{service: dependencies.AgentEdit},
			authorizer: handlers.detail.planMutationAuthorizer,
		}
		workspaceScoped.GET("/workflow/plans/:planId/agent-edit/wake", handleWorkspace(agentEdit.GetWake))
		workspaceScoped.GET("/workflow/plans/:planId/agent-edit/messages", handleWorkspace(agentEdit.ListMessages))
		workspaceScoped.GET("/workflow/plans/:planId/agent-edit/agents", handleWorkspace(agentEdit.ListAgents))
		workspaceScoped.POST("/workflow/plans/:planId/agent-edit/requests", handleWorkspaceJSON[dto.WorkflowPlanAgentEditRequest](agentEdit.CreateRequest))
	}
}

type agentEditAdapter struct {
	service AgentEditService
}

func (adapter agentEditAdapter) GetWake(ctx context.Context, workspaceID, planID string) (dto.WorkflowPlanAgentEditWakeResponse, error) {
	wake, err := adapter.service.GetWake(ctx, workspaceID, planID)
	if err != nil {
		return dto.WorkflowPlanAgentEditWakeResponse{}, err
	}
	response := dto.WorkflowPlanAgentEditWakeResponse{}
	if wake != nil {
		value := agentEditWakeResponse(*wake)
		response.Wake = &value
	}
	return response, nil
}

func (adapter agentEditAdapter) ListMessages(ctx context.Context, workspaceID, planID string, beforeSeq int64, limit int) (dto.WorkflowPlanAgentEditMessagesResponse, error) {
	page, err := adapter.service.ListMessages(ctx, workspaceID, planID, beforeSeq, limit)
	if err != nil {
		return dto.WorkflowPlanAgentEditMessagesResponse{}, err
	}
	return agentEditMessagesResponse(page), nil
}

func (adapter agentEditAdapter) ListAgents(ctx context.Context, userID, workspaceID, planID, query, cursor string, limit int) (dto.WorkflowPlanAgentEditAgentsResponse, error) {
	page, err := adapter.service.ListAgents(ctx, userID, workspaceID, planID, query, cursor, limit)
	if err != nil {
		return dto.WorkflowPlanAgentEditAgentsResponse{}, err
	}
	return agentEditAgentsResponse(page), nil
}

func (adapter agentEditAdapter) CreateRequest(ctx context.Context, input agentEditCreateInput) (dto.WorkflowPlanAgentEditRequestResponse, bool, error) {
	result, err := adapter.service.Request(ctx, planmanage.RequestInput{
		WorkspaceID: input.WorkspaceID, PlanID: input.PlanID, UserID: input.UserID,
		MemberID: input.MemberID, MemberDisplayName: input.MemberDisplayName,
		AgentID: input.Request.ResponderAgentID, ClientMessageID: input.Request.ClientMessageID,
		DraftRevision: input.Request.BaseDraftRevision, Instruction: input.Request.Instruction,
		Target: planmanage.Target{
			Kind: input.Request.Target.Kind, ID: input.Request.Target.ID,
		},
	})
	if err != nil {
		return dto.WorkflowPlanAgentEditRequestResponse{}, false, err
	}
	return agentEditRequestResponse(result), result.Inserted, nil
}
