package run

import (
	"context"
	"fmt"
	"time"

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

const (
	// WaitingReasonResponderCapacity is the stable public machine reason for a
	// selectOne effect parked without a frozen responder because every candidate
	// currently has occupied callback capacity. It intentionally carries no
	// candidate identity or pool details.
	WaitingReasonResponderCapacity = "responder_capacity"

	// Responder-capacity lifecycle events are appended by the same transactions
	// that park, select, or promote a callback effect. Their payloads contain
	// only the effect/activation/node context and this stable reason.
	EventTypeEffectWaitingForResponderCapacity = "effect_waiting_for_responder_capacity"
	EventTypeEffectResponderSelected           = "effect_responder_selected"
	EventTypeEffectResponderPromoted           = "effect_responder_promoted"
)

// appendResponderCapacityEvent appends one public-safe responder-capacity
// lifecycle event while the owning Run transaction is still holding its row
// lock. The caller must perform the effect/waiter mutation in the same
// transaction before calling this helper. Advancing the Run event cursor and
// revision here makes the event part of the atomic state transition and fences
// a concurrent reducer turn.
//
// The payload deliberately contains no candidate pool, candidate ordering,
// lease token, worker, or responder identity. A responder identity becomes
// public only through the existing authorized frozen-responder projection after
// selection and delivery.
func (store *Store) appendResponderCapacityEvent(
	ctx context.Context,
	tx coredb.Tx,
	run Run,
	effect Effect,
	nodeID string,
	eventType string,
	nowTime time.Time,
) error {
	if tx == nil {
		return storeerr.BadRequest("transaction is required")
	}
	if run.WorkspaceID == "" || run.ID == "" || effect.ID == "" || effect.RunID != run.ID {
		return storeerr.BadRequest("responder capacity lifecycle event identity is invalid")
	}
	if eventType != EventTypeEffectWaitingForResponderCapacity &&
		eventType != EventTypeEffectResponderSelected &&
		eventType != EventTypeEffectResponderPromoted {
		return storeerr.BadRequest("unknown responder capacity lifecycle event")
	}
	var activationID string
	if err := tx.QueryRow(ctx, `
SELECT logical_key
FROM workflow_run_activations
WHERE workspace_id=$1 AND run_id=$2 AND id=$3`, run.WorkspaceID, run.ID, effect.ActivationID).Scan(&activationID); err != nil {
		return fmt.Errorf("load workflow activation logical key for responder capacity event: %w", err)
	}
	payload, err := model.MarshalJSONObject("responder capacity lifecycle event", map[string]any{
		"effectId":      effect.ID,
		"activationId":  activationID,
		"nodeId":        nodeID,
		"kind":          effect.Kind,
		"waitingReason": WaitingReasonResponderCapacity,
	})
	if err != nil {
		return fmt.Errorf("encode responder capacity lifecycle event: %w", err)
	}
	draft, err := responderCapacityEventDraft(eventType, payload)
	if err != nil {
		return err
	}
	_, lastSeq, err := store.InTx(tx).insertEvents(ctx, run, []EventDraft{draft}, nowTime)
	if err != nil {
		return err
	}
	result, err := tx.Exec(ctx, `
UPDATE workflow_run_instances
SET last_event_seq=$3, revision=revision+1, updated_at=$4
WHERE workspace_id=$1 AND id=$2 AND revision=$5`, run.WorkspaceID, run.ID, lastSeq, nowTime, run.Revision)
	if err != nil {
		return fmt.Errorf("advance workflow Run responder capacity event cursor: %w", err)
	}
	if result.RowsAffected() == 0 {
		return storeerr.Conflict("workflow Run changed while appending responder capacity event")
	}
	return nil
}

// responderCapacityEventDraft keeps the three store-produced event kinds
// explicit at the producer boundary. Besides making the closed contract easy to
// audit, the switch prevents an arbitrary caller from persisting a new event
// kind through this FSM without updating the eventcontract/public projection.
func responderCapacityEventDraft(eventType string, payload []byte) (EventDraft, error) {
	switch eventType {
	case EventTypeEffectWaitingForResponderCapacity:
		return EventDraft{Type: EventTypeEffectWaitingForResponderCapacity, PayloadJSON: payload}, nil
	case EventTypeEffectResponderSelected:
		return EventDraft{Type: EventTypeEffectResponderSelected, PayloadJSON: payload}, nil
	case EventTypeEffectResponderPromoted:
		return EventDraft{Type: EventTypeEffectResponderPromoted, PayloadJSON: payload}, nil
	default:
		return EventDraft{}, storeerr.BadRequest("unknown responder capacity lifecycle event")
	}
}
