package app

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/runeffect"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

const appRuntimeDeliveryCanceledCode = "app_delivery_canceled"

func (store *Store) cancelAppRuntimeDeliveryInTx(ctx context.Context, tx coredb.Tx, workspaceID string, deliveryID string, reason string, now time.Time) (bool, error) {
	// Callers hold the service-installation row lock. Runtime claims acquire the
	// same fence before inserting an attempt, so the attempt snapshot below
	// cannot miss a concurrently created lease. Existing attempt lifecycle
	// operations use attempt -> delivery -> action ordering, which this path
	// preserves.
	attemptIDs, err := listActiveAppRuntimeAttemptIDsForCancel(ctx, tx, workspaceID, deliveryID)
	if err != nil {
		return false, err
	}
	txServiceActor := store.serviceActorStore().InTx(tx)
	for _, attemptID := range attemptIDs {
		if _, err := txServiceActor.SetDeliveryAttemptStatus(ctx, serviceactor.DeliveryAttemptStatusUpdate{
			WorkspaceID: workspaceID,
			ID:          attemptID,
			Status:      serviceactor.DeliveryAttemptStatusCanceled,
			ErrorCode:   appRuntimeDeliveryCanceledCode,
			ErrorDetail: reason,
			Now:         now,
		}); err != nil {
			return false, err
		}
	}
	result, err := tx.Exec(ctx, `
UPDATE service_event_deliveries
SET status = $3,
    completed_at = $4,
    next_retry_at = NULL,
    effect_kind = 'none',
    effect_ref = '',
    message_id = NULL,
    error_code = $5,
    error_detail = $6,
    lease_owner = '',
    lease_expires_at = NULL,
    updated_at = $4
WHERE workspace_id = $1
  AND id = $2
	  AND status IN ('running', 'pending', 'retry_waiting')
`, strings.TrimSpace(workspaceID), strings.TrimSpace(deliveryID), string(runeffect.StatusCanceled), now, appRuntimeDeliveryCanceledCode, reason)
	if err != nil {
		return false, fmt.Errorf("cancel App runtime delivery: %w", err)
	}
	if result.RowsAffected() != 1 {
		return false, nil
	}
	delivery, err := store.serviceActorStore().InTx(tx).LoadEventDeliveryForUpdate(ctx, workspaceID, deliveryID)
	if err != nil {
		return false, err
	}
	if delivery.SubscriptionKind == serviceactor.SubscriptionKindAppNotificationAction {
		if err := store.completeAppNotificationActionForDeliveryInTx(ctx, tx, delivery, appNotificationActionStateCancelled, appRuntimeDeliveryCanceledCode, nil, now); err != nil {
			return false, err
		}
		if err := store.retireAppNotificationActionSubscriptionAfterDeliveryInTx(ctx, tx, delivery, now); err != nil {
			return false, err
		}
	}
	return true, nil
}

func listActiveAppRuntimeAttemptIDsForCancel(ctx context.Context, tx coredb.Tx, workspaceID string, deliveryID string) ([]string, error) {
	rows, err := tx.Query(ctx, `
SELECT id
FROM service_event_delivery_attempts
WHERE workspace_id = $1
  AND service_event_delivery_id = $2
  AND status IN ('leased', 'delivered')
ORDER BY attempt_number DESC, id DESC
FOR UPDATE`, strings.TrimSpace(workspaceID), strings.TrimSpace(deliveryID))
	if err != nil {
		return nil, fmt.Errorf("list active App runtime attempts for cancel: %w", err)
	}
	defer rows.Close()
	ids := []string{}
	for rows.Next() {
		var id string
		if err := rows.Scan(&id); err != nil {
			return nil, fmt.Errorf("scan active App runtime attempt for cancel: %w", err)
		}
		if trimmed := strings.TrimSpace(id); trimmed != "" {
			ids = append(ids, trimmed)
		}
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate active App runtime attempts for cancel: %w", err)
	}
	return ids, nil
}
