package user

import (
	"context"
	"errors"
	"fmt"

	"github.com/google/uuid"
	"go.uber.org/zap"
)

// SoftDelete marks the account as soft-deleted with an audit trail of
// who initiated the operation, then drives the documented in-process
// cascade across `auth`, `lobby`, `notification`, and `geo`.
//
// The `accounts` row is the canonical state; cascade calls run after
// the database commit and are best-effort. Cascade failures are joined
// into the returned error and logged but never roll back the
// soft-delete: the producer signal is "this user is gone", and
// downstream cleanup is idempotent so a future retry can finish the
// job.
//
// Repeated calls on an already-soft-deleted account are no-ops: the
// store reports `false` for "row changed" and the cascade is skipped.
func (s *Service) SoftDelete(ctx context.Context, userID uuid.UUID, actor ActorRef) error {
	if userID == uuid.Nil {
		return ErrAccountNotFound
	}
	if err := actor.Validate(); err != nil {
		return err
	}

	now := s.deps.Now().UTC()
	changed, err := s.deps.Store.SoftDeleteAccount(ctx, userID, actor, now)
	if err != nil {
		return fmt.Errorf("user soft delete: %w", err)
	}
	if !changed {
		s.deps.Logger.Info("user soft delete skipped (already deleted)",
			zap.String("user_id", userID.String()),
		)
		return nil
	}
	s.deps.Logger.Info("user soft deleted",
		zap.String("user_id", userID.String()),
		zap.String("actor_type", actor.Type),
	)
	return s.runSoftDeleteCascade(ctx, userID, actor)
}

// runSoftDeleteCascade fans the soft-delete signal out to dependent
// modules in the documented order: auth → lobby → notification → geo.
// Each call's error is joined; the loop continues even after a
// failure so the remaining modules still get notified.
func (s *Service) runSoftDeleteCascade(ctx context.Context, userID uuid.UUID, actor ActorRef) error {
	var joined error
	if s.deps.SessionRevoker != nil {
		kind := SessionRevokeActorSoftDeleteAdmin
		if actor.Type == "user" {
			kind = SessionRevokeActorSoftDeleteUser
		}
		revokeActor := SessionRevokeActor{
			Kind:   kind,
			ID:     actor.ID,
			Reason: "soft delete",
		}
		if err := s.deps.SessionRevoker.RevokeAllForUser(ctx, userID, revokeActor); err != nil {
			joined = errors.Join(joined, fmt.Errorf("session revoke: %w", err))
		}
	}
	if s.deps.Lobby != nil {
		if err := s.deps.Lobby.OnUserDeleted(ctx, userID); err != nil {
			joined = errors.Join(joined, fmt.Errorf("lobby on-user-deleted: %w", err))
		}
	}
	if s.deps.Notification != nil {
		if err := s.deps.Notification.OnUserDeleted(ctx, userID); err != nil {
			joined = errors.Join(joined, fmt.Errorf("notification on-user-deleted: %w", err))
		}
	}
	if s.deps.Geo != nil {
		if err := s.deps.Geo.OnUserDeleted(ctx, userID); err != nil {
			joined = errors.Join(joined, fmt.Errorf("geo on-user-deleted: %w", err))
		}
	}
	if joined != nil {
		s.deps.Logger.Warn("soft-delete cascade returned errors",
			zap.String("user_id", userID.String()),
			zap.Error(joined),
		)
	}
	return joined
}
