package agent

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"

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

// WorkflowBindingOwnerResolver selects the human user that owns the machine
// backing an authenticated Agent.
type WorkflowBindingOwnerResolver interface {
	AgentManagedByUser(context.Context, string, string) (string, error)
}

// WorkflowBindingWorkspaceResolver performs the ordinary human workspace
// lookup for that owner user so the Binding captures the human member id, not
// the Agent's service member id.
type WorkflowBindingWorkspaceResolver interface {
	GetWorkspaceForUser(context.Context, string, string) (workspace.Summary, error)
}

type agentBindingCreateInput struct {
	Action        string          `json:"action"`
	Key           string          `json:"key"`
	DisplayName   string          `json:"displayName"`
	DescriptionMD string          `json:"descriptionMd,omitempty"`
	PlanID        string          `json:"planId"`
	PlanVersionID string          `json:"planVersionId"`
	Source        json.RawMessage `json:"source"`
	InputPolicy   json.RawMessage `json:"inputPolicy,omitempty"`
	Enabled       *bool           `json:"enabled"`
}

type agentBindingListInput struct {
	Action          string `json:"action"`
	PlanID          string `json:"planId,omitempty"`
	State           string `json:"state,omitempty"`
	IncludeArchived bool   `json:"includeArchived,omitempty"`
	Limit           *int   `json:"limit,omitempty"`
	Cursor          string `json:"cursor,omitempty"`
}

type agentTriggerIDInput struct {
	Action    string `json:"action"`
	TriggerID string `json:"triggerId"`
}

type agentBindingRevisionCreateInput struct {
	Action           string          `json:"action"`
	TriggerID        string          `json:"triggerId"`
	ExpectedRevision int64           `json:"expectedRevision"`
	PlanVersionID    string          `json:"planVersionId"`
	Source           json.RawMessage `json:"source"`
	InputPolicy      json.RawMessage `json:"inputPolicy,omitempty"`
}

type agentBindingEnableInput struct {
	Action           string `json:"action"`
	TriggerID        string `json:"triggerId"`
	Revision         int64  `json:"revision"`
	ExpectedRevision int64  `json:"expectedRevision"`
}

type agentBindingLifecycleInput struct {
	Action           string `json:"action"`
	TriggerID        string `json:"triggerId"`
	ExpectedRevision int64  `json:"expectedRevision"`
}

type agentBindingValidateInput struct {
	Action string `json:"action"`
	dto.WorkflowBindingValidateRequest
}

type agentWorkflowBindingRouteHandler struct {
	service    binding.Service
	replays    binding.CommandReplayService
	owners     WorkflowBindingOwnerResolver
	workspaces WorkflowBindingWorkspaceResolver
}

func registerAgentWorkflowBindingRoutes(router gin.IRoutes, base string, auth AgentAuthenticator, service binding.Service, owners WorkflowBindingOwnerResolver, workspaces WorkflowBindingWorkspaceResolver, recovery ...WorkflowRecoveryResetter) {
	if router == nil || auth == nil || service == nil || owners == nil || workspaces == nil || strings.TrimSpace(base) == "" {
		return
	}
	replays, ok := service.(binding.CommandReplayService)
	if !ok {
		return
	}
	handler := &agentWorkflowBindingRouteHandler{service: service, replays: replays, owners: owners, workspaces: workspaces}
	requirePrincipal := RequireAgentRuntimePrincipal(auth)
	router.POST(
		strings.TrimSuffix(base, "/")+"/workflow/trigger",
		requirePrincipal,
		api.HandleAgentScoped(
			handler.call,
			api.WithErrorClassifier(binding.ClassifyError),
			workflowRecoverySuccessOption(recovery...),
		),
	)
}

func (handler *agentWorkflowBindingRouteHandler) call(request api.AgentScopedRequest) (api.Response, error) {
	envelope, err := decodeWorkflowCLIEnvelope(
		request.Gin, agentWorkflowBindingMaxEnvelopeBytes, agentWorkflowBindingMaxJSONNesting, agentWorkflowBindingJSONError,
	)
	if err != nil {
		return api.Response{}, err
	}
	if err := validateWorkflowCLIContext(request, envelope.Context, "Trigger", "trigger"); err != nil {
		return api.Response{}, err
	}
	action, err := agentWorkflowBindingAction(envelope.Input)
	if err != nil {
		return api.Response{}, err
	}
	mutation := workflowBindingAgentMutation(action)
	idempotencyKey := ""
	requestDigest := ""
	actor := binding.Actor{Type: binding.ActorTypeAgent, ID: strings.TrimSpace(request.Principal.AgentID), MemberID: strings.TrimSpace(request.Principal.MemberID)}
	if mutation {
		idempotencyKey, err = verifyWorkflowCLIIdempotency(
			request, envelope.Context, workflowCLIObjectTrigger, action, "trigger",
		)
		if err != nil {
			return api.Response{}, err
		}
		if err := validateAgentWorkflowBindingMutationInput(action, envelope.Input); err != nil {
			return api.Response{}, err
		}
		requestDigest, err = workflowCLIBusinessRequestDigest(workflowCLIObjectTrigger, action, envelope.Input)
		if err != nil {
			return api.Response{}, agentWorkflowBindingJSONError(err)
		}
		replayed, found, err := handler.replays.ReplayCommand(request.Context(), binding.ReplayCommandInput{
			WorkspaceID: strings.TrimSpace(request.Principal.WorkspaceID), Action: internalWorkflowBindingAction(action),
			RequestDigest: requestDigest, Actor: actor, IdempotencyKey: idempotencyKey,
		})
		if err != nil {
			return api.Response{}, err
		}
		if found {
			return agentWorkflowBindingReplayResponse(action, replayed)
		}
	}

	var principal binding.Owner
	if workflowBindingAgentNeedsOwner(action) {
		principal, err = handler.resolveOwner(request.Context(), request.Principal)
		if err != nil {
			return api.Response{}, err
		}
		if strings.TrimSpace(principal.UserID) == "" || strings.TrimSpace(principal.MemberID) == "" {
			return api.Response{}, publicError(http.StatusForbidden, "binding_forbidden", "Agent has no active human execution owner in this workspace")
		}
	}

	workspaceID := strings.TrimSpace(request.Principal.WorkspaceID)
	switch action {
	case "create":
		input, err := decodeAgentWorkflowBindingJSONBytes[agentBindingCreateInput](envelope.Input)
		if err != nil {
			return api.Response{}, err
		}
		planID, err := requiredAgentPlanID(input.PlanID)
		if err != nil {
			return api.Response{}, err
		}
		planVersionID, source, inputPolicy, err := requiredAgentRevisionSpec(input.PlanVersionID, input.Source, input.InputPolicy)
		if err != nil {
			return api.Response{}, err
		}
		if input.Enabled == nil {
			return api.Response{}, publicError(http.StatusBadRequest, "binding_invalid_request", "enabled is required")
		}
		binding, revision, err := handler.service.Create(request.Context(), binding.CreateInput{
			WorkspaceID: workspaceID, PlanID: planID, Key: input.Key, DisplayName: input.DisplayName,
			DescriptionMD: input.DescriptionMD, PlanVersionID: planVersionID, Source: source,
			InputPolicy: inputPolicy, Activate: *input.Enabled, Owner: principal, Actor: actor,
			IdempotencyKey: idempotencyKey, RequestDigest: requestDigest,
		})
		if err != nil {
			return api.Response{}, err
		}
		return agentBindingRevisionMutationResponse(http.StatusCreated, binding, revision), nil
	case "list":
		input, err := decodeAgentWorkflowBindingJSONBytes[agentBindingListInput](envelope.Input)
		if err != nil {
			return api.Response{}, err
		}
		limit := 0
		if input.Limit != nil {
			if *input.Limit < 1 || *input.Limit > 100 {
				return api.Response{}, publicError(http.StatusBadRequest, "binding_invalid_limit", "limit must be between 1 and 100 when provided")
			}
			limit = *input.Limit
		}
		page, err := handler.service.List(request.Context(), binding.ListInput{
			WorkspaceID: workspaceID, PlanID: strings.TrimSpace(input.PlanID), State: strings.TrimSpace(input.State),
			IncludeArchived: input.IncludeArchived, Limit: limit, Cursor: strings.TrimSpace(input.Cursor),
		})
		if err != nil {
			return api.Response{}, err
		}
		items := make([]dto.WorkflowBindingResponse, 0, len(page.Items))
		for _, item := range page.Items {
			items = append(items, agentBindingResponse(item))
		}
		return api.OK(dto.WorkflowBindingListResponse{Items: items, NextCursor: page.NextCursor}), nil
	case "show":
		input, err := decodeAgentWorkflowBindingJSONBytes[agentTriggerIDInput](envelope.Input)
		if err != nil {
			return api.Response{}, err
		}
		bindingID, err := requiredAgentTriggerID(input.TriggerID)
		if err != nil {
			return api.Response{}, err
		}
		binding, err := handler.service.Get(request.Context(), workspaceID, bindingID)
		if err != nil {
			return api.Response{}, err
		}
		revisionRef := binding.LatestRevision
		if revisionRef == nil {
			revisionRef = binding.ActiveRevision
		}
		if revisionRef == nil || revisionRef.Revision < 1 {
			return api.Response{}, publicError(http.StatusInternalServerError, "binding_internal_error", fmt.Sprintf("Workflow Trigger %q has no inspectable revision", binding.ID))
		}
		revision, err := handler.service.GetRevision(request.Context(), workspaceID, bindingID, revisionRef.Revision)
		if err != nil {
			return api.Response{}, err
		}
		if revision.BindingID != binding.ID || revision.PlanID != binding.PlanID || revision.Revision != revisionRef.Revision {
			return api.Response{}, publicError(http.StatusInternalServerError, "binding_internal_error", fmt.Sprintf("Workflow Trigger %q revision snapshot does not match its summary", binding.ID))
		}
		return agentTriggerShowOK(binding, revision), nil
	case "validate":
		input, err := decodeAgentWorkflowBindingJSONBytes[agentBindingValidateInput](envelope.Input)
		if err != nil {
			return api.Response{}, err
		}
		planID, err := requiredAgentPlanID(input.PlanID)
		if err != nil {
			return api.Response{}, err
		}
		planVersionID, source, inputPolicy, err := requiredAgentRevisionSpec(input.PlanVersionID, input.Source, input.InputPolicy)
		if err != nil {
			return api.Response{}, err
		}
		validation, err := handler.service.Validate(request.Context(), binding.ValidateInput{
			WorkspaceID: workspaceID, PlanID: planID, PlanVersionID: planVersionID, Source: source, InputPolicy: inputPolicy,
		})
		if err != nil {
			return api.Response{}, err
		}
		return api.OK(agentBindingValidationResponse(validation)), nil
	case "update":
		input, err := decodeAgentWorkflowBindingJSONBytes[agentBindingRevisionCreateInput](envelope.Input)
		if err != nil {
			return api.Response{}, err
		}
		bindingID, err := requiredAgentMutationTarget(input.TriggerID, input.ExpectedRevision)
		if err != nil {
			return api.Response{}, err
		}
		planVersionID, source, inputPolicy, err := requiredAgentRevisionSpec(input.PlanVersionID, input.Source, input.InputPolicy)
		if err != nil {
			return api.Response{}, err
		}
		binding, revision, err := handler.service.CreateRevision(request.Context(), binding.CreateRevisionInput{
			WorkspaceID: workspaceID, BindingID: bindingID, ExpectedRevision: input.ExpectedRevision,
			PlanVersionID: planVersionID, Source: source, InputPolicy: inputPolicy,
			Principal: principal, Actor: actor, IdempotencyKey: idempotencyKey, RequestDigest: requestDigest,
		})
		if err != nil {
			return api.Response{}, err
		}
		return agentBindingRevisionMutationResponse(http.StatusCreated, binding, revision), nil
	case "enable":
		input, err := decodeAgentWorkflowBindingJSONBytes[agentBindingEnableInput](envelope.Input)
		if err != nil {
			return api.Response{}, err
		}
		bindingID, err := requiredAgentMutationTarget(input.TriggerID, input.ExpectedRevision)
		if err != nil {
			return api.Response{}, err
		}
		if input.Revision < 1 {
			return api.Response{}, publicError(http.StatusBadRequest, "binding_invalid_revision", "revision must be positive")
		}
		binding, revision, err := handler.service.Activate(request.Context(), binding.ActivateInput{
			WorkspaceID: workspaceID, BindingID: bindingID, Revision: input.Revision,
			ExpectedRevision: input.ExpectedRevision, Principal: principal, Actor: actor,
			IdempotencyKey: idempotencyKey, RequestDigest: requestDigest,
		})
		if err != nil {
			return api.Response{}, err
		}
		return agentBindingRevisionMutationResponse(http.StatusOK, binding, revision), nil
	case "disable", "archive":
		input, err := decodeAgentWorkflowBindingJSONBytes[agentBindingLifecycleInput](envelope.Input)
		if err != nil {
			return api.Response{}, err
		}
		bindingID, err := requiredAgentMutationTarget(input.TriggerID, input.ExpectedRevision)
		if err != nil {
			return api.Response{}, err
		}
		serviceInput := binding.LifecycleInput{
			WorkspaceID: workspaceID, BindingID: bindingID, ExpectedRevision: input.ExpectedRevision,
			Principal: principal, Actor: actor, IdempotencyKey: idempotencyKey, RequestDigest: requestDigest,
		}
		var binding binding.Binding
		switch action {
		case "disable":
			binding, err = handler.service.Disable(request.Context(), serviceInput)
		case "archive":
			binding, err = handler.service.Archive(request.Context(), serviceInput)
		}
		if err != nil {
			return api.Response{}, err
		}
		return agentBindingOK(binding), nil
	default:
		return api.Response{}, publicError(http.StatusNotFound, "binding_action_not_found", "unknown Workflow Binding action")
	}
}

func internalWorkflowBindingAction(action string) string {
	switch action {
	case "update":
		return "revise"
	case "enable":
		return "activate"
	default:
		return action
	}
}

func validateAgentWorkflowBindingMutationInput(action string, raw json.RawMessage) error {
	switch action {
	case "create":
		input, err := decodeAgentWorkflowBindingJSONBytes[agentBindingCreateInput](raw)
		if err != nil {
			return err
		}
		if strings.TrimSpace(input.Key) == "" || strings.TrimSpace(input.DisplayName) == "" {
			return publicError(http.StatusBadRequest, "binding_invalid_request", "key and displayName are required")
		}
		if _, err := requiredAgentPlanID(input.PlanID); err != nil {
			return err
		}
		if _, _, _, err := requiredAgentRevisionSpec(input.PlanVersionID, input.Source, input.InputPolicy); err != nil {
			return err
		}
		if input.Enabled == nil {
			return publicError(http.StatusBadRequest, "binding_invalid_request", "enabled is required")
		}
	case "update":
		input, err := decodeAgentWorkflowBindingJSONBytes[agentBindingRevisionCreateInput](raw)
		if err != nil {
			return err
		}
		if _, err := requiredAgentMutationTarget(input.TriggerID, input.ExpectedRevision); err != nil {
			return err
		}
		if _, _, _, err := requiredAgentRevisionSpec(input.PlanVersionID, input.Source, input.InputPolicy); err != nil {
			return err
		}
	case "enable":
		input, err := decodeAgentWorkflowBindingJSONBytes[agentBindingEnableInput](raw)
		if err != nil {
			return err
		}
		if _, err := requiredAgentMutationTarget(input.TriggerID, input.ExpectedRevision); err != nil {
			return err
		}
		if input.Revision < 1 {
			return publicError(http.StatusBadRequest, "binding_invalid_revision", "revision must be positive")
		}
	case "disable", "archive":
		input, err := decodeAgentWorkflowBindingJSONBytes[agentBindingLifecycleInput](raw)
		if err != nil {
			return err
		}
		if _, err := requiredAgentMutationTarget(input.TriggerID, input.ExpectedRevision); err != nil {
			return err
		}
	default:
		return publicError(http.StatusNotFound, "binding_action_not_found", "unknown Workflow Binding action")
	}
	return nil
}

func agentWorkflowBindingReplayResponse(action string, result binding.ReplayCommandResult) (api.Response, error) {
	switch action {
	case "create", "update", "enable":
		if result.Revision == nil {
			return api.Response{}, publicError(http.StatusInternalServerError, "binding_internal_error", fmt.Sprintf("Workflow Binding %s receipt is missing its revision snapshot", action))
		}
		status := http.StatusOK
		if action == "create" || action == "update" {
			status = http.StatusCreated
		}
		return agentBindingRevisionMutationResponse(status, result.Binding, *result.Revision), nil
	case "disable", "archive":
		return agentBindingOK(result.Binding), nil
	default:
		return api.Response{}, publicError(http.StatusInternalServerError, "binding_internal_error", fmt.Sprintf("unsupported Workflow Binding replay action %q", action))
	}
}

func (handler *agentWorkflowBindingRouteHandler) resolveOwner(ctx context.Context, authenticated storeagent.AuthenticatedAgent) (binding.Owner, error) {
	workspaceID := strings.TrimSpace(authenticated.WorkspaceID)
	ownerUserID, err := handler.owners.AgentManagedByUser(ctx, workspaceID, strings.TrimSpace(authenticated.AgentID))
	if err != nil {
		return binding.Owner{}, err
	}
	ownerUserID = strings.TrimSpace(ownerUserID)
	if ownerUserID == "" {
		return binding.Owner{}, publicError(http.StatusForbidden, "binding_forbidden", "Agent has no human execution owner")
	}
	membership, err := handler.workspaces.GetWorkspaceForUser(ctx, ownerUserID, workspaceID)
	if err != nil {
		return binding.Owner{}, err
	}
	if strings.TrimSpace(membership.WorkspaceID) != workspaceID || strings.TrimSpace(membership.MemberUserID) != ownerUserID {
		return binding.Owner{}, publicError(http.StatusForbidden, "binding_forbidden", "Agent owner does not have a matching workspace membership")
	}
	return binding.Owner{UserID: ownerUserID, MemberID: strings.TrimSpace(membership.MemberID)}, nil
}

func workflowBindingAgentMutation(action string) bool {
	switch action {
	case "create", "update", "enable", "disable", "archive":
		return true
	default:
		return false
	}
}

func workflowBindingAgentNeedsOwner(action string) bool {
	mutation := workflowBindingAgentMutation(action)
	return mutation
}

func agentWorkflowBindingAction(raw json.RawMessage) (string, error) {
	var discriminator struct {
		Action string `json:"action"`
	}
	if err := json.Unmarshal(raw, &discriminator); err != nil {
		return "", agentWorkflowBindingJSONError(err)
	}
	action := discriminator.Action
	if strings.TrimSpace(action) == "" {
		return "", publicError(http.StatusBadRequest, "binding_invalid_request", "action is required")
	}
	if strings.TrimSpace(action) != action {
		return "", publicError(http.StatusBadRequest, "binding_invalid_request", "action must use its canonical form")
	}
	switch action {
	case "create", "list", "show", "validate", "update", "enable", "disable", "archive":
		return action, nil
	default:
		return "", publicError(http.StatusNotFound, "binding_action_not_found", "unknown Workflow Binding action")
	}
}

func requiredAgentTriggerID(raw string) (string, error) {
	bindingID := strings.TrimSpace(raw)
	if bindingID == "" {
		return "", publicError(http.StatusBadRequest, "binding_invalid_request", "triggerId is required")
	}
	return bindingID, nil
}

func requiredAgentMutationTarget(raw string, revision int64) (string, error) {
	bindingID, err := requiredAgentTriggerID(raw)
	if err != nil {
		return "", err
	}
	if revision < 1 {
		return "", publicError(http.StatusBadRequest, "binding_invalid_request", "expectedRevision must be positive")
	}
	return bindingID, nil
}
