package app

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

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

const (
	appNotificationUninstallExecutionActor = "app_installation_uninstall"
	appNotificationUninstallResultCode     = "installation_uninstalled"
)

// terminalizeAppNotificationsBeforeAppInstallationDelete closes the durable
// notification lifecycle before the App's hot service-actor substrate is
// deleted. Notification, message, mutation, and Human-action rows remain as
// history after the installation, member, binding, subscriptions, and runtime
// delivery rows have been removed.
func (store *Store) terminalizeAppNotificationsBeforeAppInstallationDelete(
	ctx context.Context,
	tx coredb.Tx,
	workspaceID string,
	installationID string,
	causedByMemberID string,
	now time.Time,
) error {
	workspaceID = strings.TrimSpace(workspaceID)
	installationID = strings.TrimSpace(installationID)
	causedByMemberID = strings.TrimSpace(causedByMemberID)
	if workspaceID == "" || installationID == "" || causedByMemberID == "" {
		return badRequest("workspace, installation, and uninstalling member are required")
	}
	if now.IsZero() {
		now = time.Now().UTC()
	} else {
		now = now.UTC()
	}

	// First fence every action delivery while its service-actor rows still
	// exist. Installation deletion will subsequently cascade the hot delivery
	// ledger, while the action record below keeps its terminal audit outcome.
	deliveryIDs, err := listActiveAppNotificationActionDeliveryIDsForUninstall(ctx, tx, workspaceID, installationID)
	if err != nil {
		return err
	}
	for _, deliveryID := range deliveryIDs {
		if _, err := store.cancelAppRuntimeDeliveryInTx(ctx, tx, workspaceID, deliveryID, "App installation was uninstalled", now); err != nil {
			return err
		}
	}
	if _, err := tx.Exec(ctx, `
UPDATE service_notification_actions action_record
JOIN service_notifications notification
  ON notification.id = action_record.service_notification_id
 AND notification.workspace_id = action_record.workspace_id
SET action_record.state = 'cancelled',
    action_record.result_code = $3,
    action_record.result_digest = '',
    action_record.completed_at = $4,
    action_record.updated_at = $4
WHERE notification.workspace_id = $1
  AND notification.service_installation_id = $2
  AND action_record.state IN ('accepted', 'delivered')
`, workspaceID, installationID, appNotificationUninstallResultCode, now); err != nil {
		return fmt.Errorf("terminalize App notification actions for App uninstall: %w", err)
	}

	notificationIDs, err := listActiveAppNotificationIDsForUninstall(ctx, tx, workspaceID, installationID)
	if err != nil {
		return err
	}
	for _, notificationID := range notificationIDs {
		notification, err := store.LoadAppNotificationInTx(ctx, tx, workspaceID, notificationID, true)
		if err != nil {
			return err
		}
		if notification.Status != AppNotificationStatusActive {
			continue
		}
		installation, serviceMember, binding, err := store.authorizeExistingAppNotificationInTx(ctx, tx, notification, AppNotificationMutationExpire)
		if err != nil {
			return err
		}
		if err := store.closeAppNotificationActionSubscriptionsInTx(ctx, tx, notification, "", now); err != nil {
			return err
		}
		existingMessage, err := message.NewScopeRepositories(tx).Messages.LoadByID(ctx, notification.WorkspaceID, notification.ScopeID, notification.MessageID)
		if err != nil {
			return err
		}
		messageMutation, err := message.UpdateServiceNotificationMessageTx(ctx, tx, message.ServiceNotificationMessageUpdate{
			WorkspaceID: notification.WorkspaceID, ScopeID: notification.ScopeID, MessageID: notification.MessageID,
			AuthorMemberID: serviceMember.MemberID, ExpectedEditRevision: &existingMessage.EditRevision,
			Content: existingMessage.Content, Metadata: existingMessage.Metadata, UpdatedAt: now,
		})
		if err != nil {
			return err
		}

		if notification.ContentKind == AppNotificationContentInteractiveCard {
			card, err := store.LoadAppNotificationCardByNotificationInTx(ctx, tx, notification.ID, true)
			if err != nil {
				return err
			}
			nextCard := card
			nextCard.CardRevision++
			nextCard.State = AppNotificationCardStateExpired
			nextCard.ExpiresAt = &now
			nextCard.UpdatedAt = now
			changed, err := store.UpdateAppNotificationCardInTx(ctx, tx, nextCard, card.CardRevision, card.State)
			if err != nil {
				return err
			}
			if !changed {
				return conflict("App notification card changed during App deletion")
			}
		}

		correlationID, err := identifiers.NewEntityID("cor")
		if err != nil {
			return fmt.Errorf("generate App notification uninstall correlation id: %w", err)
		}
		next := notification
		next.Status = AppNotificationStatusExpired
		next.Revision++
		next.ExpiresAt = &now
		next.CausedByActorType = "human"
		next.CausedByActorID = causedByMemberID
		next.ExecutionActorType = "system"
		next.ExecutionActorID = appNotificationUninstallExecutionActor
		next.CorrelationID = correlationID
		next.UpdatedAt = now
		changed, err := store.UpdateAppNotificationInTx(ctx, tx, next, notification.Revision, notification.Status)
		if err != nil {
			return err
		}
		if !changed {
			return conflict("App notification changed during App deletion")
		}

		result, err := store.appNotificationResultInTx(ctx, tx, next)
		if err != nil {
			return err
		}
		if messageMutation.Message.ID != "" {
			appMessage, err := store.appNotificationMutationAppMessageInTx(ctx, tx, next, binding, installation, serviceMember, messageMutation.Message)
			if err != nil {
				return err
			}
			result.MessageMutation = &appMessage
			result.MessageMutationKind = "update"
			result.MessageMutationIsLatestVisible = messageMutation.IsLatestVisibleMessage
		}
		resultState, err := appNotificationDeterministicJSON(result)
		if err != nil {
			return err
		}
		payloadDigest, err := appNotificationDigest(struct {
			InstallationID string `json:"installationId"`
			Reason         string `json:"reason"`
		}{InstallationID: installationID, Reason: appNotificationUninstallResultCode})
		if err != nil {
			return err
		}
		mutationID, err := identifiers.NewEntityID("btnm")
		if err != nil {
			return fmt.Errorf("generate App notification uninstall mutation id: %w", err)
		}
		completedAt := now
		if err := store.InsertAppNotificationMutationInTx(ctx, tx, AppNotificationMutationRecord{
			ID: mutationID, WorkspaceID: workspaceID, AppNotificationID: next.ID,
			MutationKind: AppNotificationMutationExpire, ClientMutationID: "app-installation-uninstall",
			ExpectedRevision: notification.Revision, ResultRevision: next.Revision,
			PayloadDigest: payloadDigest, ResultState: resultState, MessageEditRevision: messageMutation.Message.EditRevision,
			CausedByActorType: next.CausedByActorType, CausedByActorID: next.CausedByActorID,
			ExecutionActorType: next.ExecutionActorType, ExecutionActorID: next.ExecutionActorID,
			CorrelationID: correlationID, CreatedAt: now, CompletedAt: &completedAt,
		}); err != nil {
			return err
		}
		if err := store.enqueueAppNotificationMutationFanoutInTx(ctx, tx, mutationID, result, now); err != nil {
			return err
		}
	}
	return nil
}

func listActiveAppNotificationActionDeliveryIDsForUninstall(ctx context.Context, tx coredb.Tx, workspaceID, installationID string) ([]string, error) {
	rows, err := tx.Query(ctx, `
SELECT id
FROM service_event_deliveries
WHERE workspace_id = $1
  AND service_installation_id = $2
  AND subscription_kind = $3
  AND status IN ('running', 'pending', 'retry_waiting')
ORDER BY created_at ASC, id ASC
`, workspaceID, installationID, serviceactor.SubscriptionKindAppNotificationAction)
	if err != nil {
		return nil, fmt.Errorf("list active App notification action deliveries for App uninstall: %w", err)
	}
	defer rows.Close()
	var ids []string
	for rows.Next() {
		var id string
		if err := rows.Scan(&id); err != nil {
			return nil, fmt.Errorf("scan active App notification action delivery for App uninstall: %w", err)
		}
		ids = append(ids, id)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate active App notification action deliveries for App uninstall: %w", err)
	}
	return ids, nil
}

func listActiveAppNotificationIDsForUninstall(ctx context.Context, tx coredb.Tx, workspaceID, installationID string) ([]string, error) {
	rows, err := tx.Query(ctx, `
SELECT id
FROM service_notifications
WHERE workspace_id = $1
  AND service_installation_id = $2
  AND status = 'active'
ORDER BY id ASC
FOR UPDATE
`, workspaceID, installationID)
	if err != nil {
		return nil, fmt.Errorf("list active App notifications for App uninstall: %w", err)
	}
	defer rows.Close()
	var ids []string
	for rows.Next() {
		var id string
		if err := rows.Scan(&id); err != nil {
			return nil, fmt.Errorf("scan active App notification for App uninstall: %w", err)
		}
		ids = append(ids, id)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate active App notifications for App uninstall: %w", err)
	}
	return ids, nil
}
