package planmanage

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/agentops"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

const maxAgentReplyBytes = 32 * 1024

func (service *AgentEditService) ResolveAgentWorkflowReplyTargetScopeID(
	ctx context.Context,
	author agent.AuthenticatedAgent,
	planID string,
) (string, error) {
	if err := service.validate(); err != nil {
		return "", err
	}
	author, planID, err := normalizeReplyIdentity(author, planID)
	if err != nil {
		return "", err
	}
	plan, err := service.plans.LoadPlan(ctx, author.WorkspaceID, planID)
	if err != nil {
		return "", workflowReplyPlanError(err)
	}
	if strings.TrimSpace(plan.ScopeID) == "" {
		return "", message.ErrForbidden
	}
	if _, err := service.agents.LoadWorkflowAgentReplyAuthor(
		ctx, service.db, author.WorkspaceID, author.AgentID, author.MemberID,
	); err != nil {
		return "", workflowReplyAuthorError(err)
	}
	if err := message.RequireWorkflowScope(ctx, service.db, author.WorkspaceID, plan.ScopeID); err != nil {
		return "", err
	}
	if err := message.RequireWorkflowScopeParticipant(ctx, service.db, plan.ScopeID, author.MemberID); err != nil {
		return "", err
	}
	return plan.ScopeID, nil
}

func (service *AgentEditService) SendAgentWorkflowReply(
	ctx context.Context,
	author agent.AuthenticatedAgent,
	planID, content, clientMessageID string,
	freshness message.SendMessageFreshness,
) (message.SendMessageResult, error) {
	if err := service.validate(); err != nil {
		return message.SendMessageResult{}, err
	}
	author, planID, err := normalizeReplyIdentity(author, planID)
	if err != nil {
		return message.SendMessageResult{}, err
	}
	content = strings.TrimSpace(content)
	clientMessageID = strings.TrimSpace(clientMessageID)
	if content == "" || len(content) > maxAgentReplyBytes {
		return message.SendMessageResult{}, fmt.Errorf("%w: workflow reply content is required and must be at most 32768 bytes", domain.ErrInvalidArgument)
	}
	if clientMessageID == "" || len(clientMessageID) > MaxClientMessageID || !freshness.Enabled() {
		return message.SendMessageResult{}, fmt.Errorf("%w: workflow replies require clientMessageId and current-target freshness", domain.ErrInvalidArgument)
	}

	input := agentWorkflowReplyInput{
		author: author, planID: planID, content: content,
		clientMessageID: clientMessageID, freshness: freshness,
	}
	var result message.SendMessageResult
	err = coredb.WithTx(ctx, service.db, func(tx coredb.Tx) error {
		var txErr error
		result, txErr = service.sendAgentWorkflowReplyTx(ctx, tx, input)
		return txErr
	})
	return result, err
}

type agentWorkflowReplyInput struct {
	author          agent.AuthenticatedAgent
	planID          string
	content         string
	clientMessageID string
	freshness       message.SendMessageFreshness
}

type agentWorkflowReplyContext struct {
	input         agentWorkflowReplyInput
	scopeID       string
	selected      app.WorkflowAgentEditAgent
	request       message.ScopeMessageRecord
	payloadDigest string
}

func (service *AgentEditService) sendAgentWorkflowReplyTx(
	ctx context.Context,
	tx coredb.Tx,
	input agentWorkflowReplyInput,
) (message.SendMessageResult, error) {
	prepared, replay, err := service.prepareAgentWorkflowReply(ctx, tx, input)
	if err != nil || replay != nil {
		if replay != nil {
			return *replay, err
		}
		return message.SendMessageResult{}, err
	}
	held, err := service.authorizeAgentWorkflowReply(ctx, tx, &prepared)
	if err != nil || held != nil {
		if held != nil {
			return *held, err
		}
		return message.SendMessageResult{}, err
	}
	return service.writeAgentWorkflowReply(ctx, tx, prepared)
}

func (service *AgentEditService) prepareAgentWorkflowReply(
	ctx context.Context,
	tx coredb.Tx,
	input agentWorkflowReplyInput,
) (agentWorkflowReplyContext, *message.SendMessageResult, error) {
	plan, err := service.plans.InTx(tx).LoadPlanForUpdate(
		ctx, input.author.WorkspaceID, input.planID,
	)
	if err != nil {
		return agentWorkflowReplyContext{}, nil, workflowReplyPlanError(err)
	}
	scopeID := strings.TrimSpace(plan.ScopeID)
	if scopeID == "" || strings.TrimSpace(input.freshness.TargetKey) != scopeID {
		return agentWorkflowReplyContext{}, nil, message.ErrForbidden
	}
	selected, err := service.agents.LoadWorkflowAgentReplyAuthor(
		ctx, tx, input.author.WorkspaceID, input.author.AgentID, input.author.MemberID,
	)
	if err != nil {
		return agentWorkflowReplyContext{}, nil, workflowReplyAuthorError(err)
	}
	if err := message.RequireWorkflowScope(ctx, tx, input.author.WorkspaceID, scopeID); err != nil {
		return agentWorkflowReplyContext{}, nil, err
	}
	if err := message.RequireWorkflowScopeParticipant(ctx, tx, scopeID, input.author.MemberID); err != nil {
		return agentWorkflowReplyContext{}, nil, err
	}
	payloadDigest, err := message.ComputeMessageCreatePayloadDigest(message.MessageCreateDigestInput{
		MessageType: "text",
		Content:     input.content,
	})
	if err != nil {
		return agentWorkflowReplyContext{}, nil, err
	}
	prepared := agentWorkflowReplyContext{
		input: input, scopeID: scopeID, selected: selected, payloadDigest: payloadDigest,
	}
	replay, found, err := message.FindWorkflowScopeMessageByClientID(
		ctx, tx, scopeID, input.author.MemberID, input.clientMessageID,
	)
	if err != nil || !found {
		return prepared, nil, err
	}
	if !strings.EqualFold(strings.TrimSpace(replay.IdempotencyPayloadDigest), payloadDigest) {
		return agentWorkflowReplyContext{}, nil, message.ErrConflict
	}
	result := message.SendMessageResult{
		State: message.SendStateAlreadySent, MessageID: replay.ID, TargetKey: scopeID,
		ClientMessageID: input.clientMessageID, SeenUpToSeq: input.freshness.SeenUpToSeq,
		AcceptedSeq: replay.Seq, LatestSeq: replay.Seq,
	}
	return agentWorkflowReplyContext{}, &result, nil
}

func (service *AgentEditService) authorizeAgentWorkflowReply(
	ctx context.Context,
	tx coredb.Tx,
	prepared *agentWorkflowReplyContext,
) (*message.SendMessageResult, error) {
	input := prepared.input
	request, found, err := message.LoadLatestWorkflowEditRequestForUpdate(
		ctx, tx, input.author.WorkspaceID, prepared.scopeID,
	)
	if err != nil {
		return nil, err
	}
	if !found || !containsReplySource(input.freshness.SourceMessageIDs, request.ID) {
		return nil, message.ErrForbidden
	}
	requestMetadata, err := message.DecodeWorkflowEditRequestMetadata(request.Metadata)
	if err != nil {
		return nil, err
	}
	if requestMetadata.PlanID != input.planID ||
		requestMetadata.ResponderAgentID != input.author.AgentID {
		return nil, message.ErrForbidden
	}
	delivered, err := message.NewScopeRepositories(tx).Outbox.HasAgentDeliveryReceipt(
		ctx, message.DeliverySourceRefMessage, request.ID, input.author.AgentID,
	)
	if err != nil {
		return nil, err
	}
	if !delivered {
		return nil, message.ErrForbidden
	}
	wake, err := service.ops.InTx(tx).LoadMessageWakeState(
		ctx, input.author.WorkspaceID, input.author.AgentID, request.ID,
	)
	if err != nil {
		return nil, err
	}
	if !wake.Active || request.Seq > input.freshness.SeenUpToSeq {
		return nil, message.ErrForbidden
	}
	replied, err := message.HasWorkflowScopeAgentReplyAfter(
		ctx, tx, input.author.WorkspaceID, prepared.scopeID,
		input.author.MemberID, request.Seq,
	)
	if err != nil {
		return nil, err
	}
	if replied {
		return nil, message.ErrForbidden
	}
	latestSeq, err := message.LatestWorkflowScopeMessageSeq(ctx, tx, prepared.scopeID)
	if err != nil {
		return nil, err
	}
	if latestSeq > input.freshness.SeenUpToSeq {
		held := message.SendMessageResult{
			State: message.SendStateHeld, TargetKey: prepared.scopeID,
			ClientMessageID: input.clientMessageID,
			SeenUpToSeq:     input.freshness.SeenUpToSeq,
			LatestSeq:       latestSeq, NeedsRefreshUntilSeq: latestSeq,
		}
		return &held, nil
	}
	prepared.request = request
	return nil, nil
}

func (service *AgentEditService) writeAgentWorkflowReply(
	ctx context.Context,
	tx coredb.Tx,
	prepared agentWorkflowReplyContext,
) (message.SendMessageResult, error) {
	input := prepared.input
	written, err := message.WriteWorkflowScopeMessageTx(ctx, tx, message.WorkflowScopeMessageWrite{
		WorkspaceID: input.author.WorkspaceID, ScopeID: prepared.scopeID,
		ChannelName: input.planID, AuthorMemberID: input.author.MemberID,
		AuthorType: message.AuthorActorTypeAgent, ClientMessageID: input.clientMessageID,
		PayloadDigest: prepared.payloadDigest, Content: input.content,
		AuthorProfile: message.AuthorProfile{
			ActorType: message.AuthorActorTypeAgent, PrincipalID: input.author.AgentID,
			MemberID: input.author.MemberID, TargetID: strings.TrimSpace(prepared.selected.Name),
			DisplayName: firstReplyValue(prepared.selected.DisplayName, prepared.selected.Name),
			AvatarURL:   strings.TrimSpace(prepared.selected.AvatarURL),
		},
	})
	if err != nil {
		return message.SendMessageResult{}, err
	}
	if written.Inserted {
		if err := service.ops.InTx(tx).RecordAgentRuntimeEvent(ctx, agent.AgentRuntimeEvent{
			WorkspaceID: input.author.WorkspaceID, AgentID: input.author.AgentID,
			MachineID: prepared.selected.MachineID, MessageID: prepared.request.ID,
			EventClass: agentops.AgentRuntimeEventClassVisibleReplySent,
			Target:     "workflow:" + input.planID, ReplyMessageID: written.Message.ID,
			OccurredAt: written.Message.CreatedAt,
		}); err != nil {
			return message.SendMessageResult{}, err
		}
	}
	state := message.SendStateSent
	if !written.Inserted {
		state = message.SendStateAlreadySent
	}
	return message.SendMessageResult{
		State: state, MessageID: written.Message.ID, TargetKey: prepared.scopeID,
		ClientMessageID: input.clientMessageID, SeenUpToSeq: input.freshness.SeenUpToSeq,
		AcceptedSeq: written.Message.Seq, LatestSeq: written.Message.Seq,
		Delivery: written.Delivery,
	}, nil
}

func normalizeReplyIdentity(
	author agent.AuthenticatedAgent,
	planID string,
) (agent.AuthenticatedAgent, string, error) {
	author.AgentID = strings.TrimSpace(author.AgentID)
	author.WorkspaceID = strings.TrimSpace(author.WorkspaceID)
	author.MemberID = strings.TrimSpace(author.MemberID)
	planID = strings.TrimSpace(planID)
	if author.AgentID == "" || author.WorkspaceID == "" || author.MemberID == "" || planID == "" {
		return agent.AuthenticatedAgent{}, "", fmt.Errorf("%w: workflow reply Agent, workspace, member, and Plan are required", domain.ErrInvalidArgument)
	}
	return author, planID, nil
}

func workflowReplyAuthorError(err error) error {
	if errors.Is(err, app.ErrNotFound) {
		return message.ErrForbidden
	}
	return err
}

func workflowReplyPlanError(err error) error {
	if errors.Is(err, storeerr.ErrNotFound) || errors.Is(err, storeerr.ErrForbidden) {
		return message.ErrForbidden
	}
	return err
}

func containsReplySource(values []string, expected string) bool {
	expected = strings.TrimSpace(expected)
	for _, value := range values {
		if strings.TrimSpace(value) == expected {
			return true
		}
	}
	return false
}

func firstReplyValue(values ...string) string {
	for _, value := range values {
		if value = strings.TrimSpace(value); value != "" {
			return value
		}
	}
	return ""
}
