package run

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

// WorkflowRunMessageBinding atomically links one committed transcript message
// to the exact immutable send_message effect that produced it. The caller must
// use the message creation transaction so a visible message is never observed
// without its native Workflow source.
type WorkflowRunMessageBinding struct {
	WorkspaceID     string
	RunID           string
	EffectID        string
	MessageID       string
	ClientMessageID string
	Now             time.Time
}

// BindWorkflowRunMessage binds a message without resolving the effect receipt.
// The first binding requires a live dispatch boundary; an exact later replay is
// idempotent even after the dispatcher advanced the effect to another status.
func (store *TxStore) BindWorkflowRunMessage(ctx context.Context, params WorkflowRunMessageBinding) (Effect, error) {
	if err := store.Validate(); err != nil {
		return Effect{}, err
	}
	params.WorkspaceID = strings.TrimSpace(params.WorkspaceID)
	params.RunID = strings.TrimSpace(params.RunID)
	params.EffectID = strings.TrimSpace(params.EffectID)
	params.MessageID = strings.TrimSpace(params.MessageID)
	params.ClientMessageID = strings.TrimSpace(params.ClientMessageID)
	if params.WorkspaceID == "" || params.RunID == "" || params.EffectID == "" || params.MessageID == "" ||
		params.ClientMessageID == "" || len(params.MessageID) > 255 || len(params.ClientMessageID) > 128 {
		return Effect{}, storeerr.BadRequest("complete bounded workflow Run message binding is required")
	}

	effect, err := scanEffect(store.tx.QueryRow(ctx, effectSelectSQL()+`
WHERE workspace_id=$1 AND run_id=$2 AND id=$3 FOR UPDATE`, params.WorkspaceID, params.RunID, params.EffectID))
	if err != nil {
		return Effect{}, mapReadError("lock workflow Run message effect", err)
	}
	if effect.Kind != EffectKindMessage {
		return Effect{}, storeerr.Conflict("workflow Run effect is not a message effect")
	}
	if err := validateFrozenMessageBindingRequest(effect, params); err != nil {
		return Effect{}, err
	}
	if effect.MessageID != "" {
		if effect.MessageID != params.MessageID {
			return Effect{}, storeerr.Conflict("workflow Run message effect is already bound to a different message")
		}
		return effect, nil
	}
	if effect.Status != EffectStatusDispatching {
		return Effect{}, storeerr.Conflict("workflow Run message effect is not dispatching")
	}
	if params.Now.IsZero() {
		params.Now = time.Now()
	}
	params.Now = utc(params.Now)
	result, err := store.tx.Exec(ctx, `
UPDATE workflow_run_effects
SET message_id=$4,updated_at=$5
WHERE workspace_id=$1 AND run_id=$2 AND id=$3
  AND effect_kind='send_message' AND status='dispatching' AND message_id IS NULL`,
		params.WorkspaceID, params.RunID, params.EffectID, params.MessageID, params.Now)
	if err != nil {
		if coredb.IsUniqueViolation(err) {
			return Effect{}, storeerr.Conflict("message is already bound to a different workflow Run effect")
		}
		if coredb.IsForeignKeyViolation(err) {
			return Effect{}, storeerr.Conflict("workflow Run message binding references an uncommitted message")
		}
		return Effect{}, fmt.Errorf("bind workflow Run message effect: %w", err)
	}
	if result.RowsAffected() == 0 {
		return Effect{}, storeerr.Conflict("workflow Run message effect binding changed")
	}
	effect.MessageID = params.MessageID
	effect.UpdatedAt = params.Now
	return effect, nil
}

func validateFrozenMessageBindingRequest(effect Effect, params WorkflowRunMessageBinding) error {
	var request struct {
		Intent struct {
			Kind string `json:"kind"`
		} `json:"intent"`
		Message *struct {
			WorkspaceID     string `json:"workspaceId"`
			RunID           string `json:"runId"`
			EffectID        string `json:"effectId"`
			ClientMessageID string `json:"clientMessageId"`
		} `json:"message"`
	}
	if err := json.Unmarshal(effect.RequestJSON, &request); err != nil {
		return storeerr.Conflict("workflow Run message effect request is invalid")
	}
	if strings.TrimSpace(request.Intent.Kind) != EffectKindMessage || request.Message == nil ||
		strings.TrimSpace(request.Message.WorkspaceID) != params.WorkspaceID ||
		strings.TrimSpace(request.Message.RunID) != params.RunID ||
		strings.TrimSpace(request.Message.EffectID) != params.EffectID ||
		strings.TrimSpace(request.Message.ClientMessageID) != params.ClientMessageID {
		return storeerr.Conflict("workflow Run message binding does not match the frozen effect request")
	}
	return nil
}
