package repository

import (
	"context"
	"crypto/sha256"
	"database/sql"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"time"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
	"gitlab.com/patrickbritton3/sojorn/go-backend/internal/models"
)

type UserRepository struct {
	pool *pgxpool.Pool
}

func NewUserRepository(pool *pgxpool.Pool) *UserRepository {
	return &UserRepository{pool: pool}
}

func (r *UserRepository) Pool() *pgxpool.Pool {
	return r.pool
}

func (r *UserRepository) CreateProfile(ctx context.Context, profile *models.Profile) error {
	query := `
		INSERT INTO public.profiles (id, handle, display_name, bio, origin_country, birth_month, birth_year)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
	`
	_, err := r.pool.Exec(ctx, query, profile.ID, profile.Handle, profile.DisplayName, profile.Bio, profile.OriginCountry, profile.BirthMonth, profile.BirthYear)
	if err != nil {
		return fmt.Errorf("failed to create profile: %w", err)
	}

	// Initialize trust state (mimicking the trigger if we want to do it in Go)
	trustQuery := `
		INSERT INTO public.trust_state (user_id, harmony_score, tier)
		VALUES ($1, 50, 'new')
	`
	_, err = r.pool.Exec(ctx, trustQuery, profile.ID)
	if err != nil {
		return fmt.Errorf("failed to initialize trust state: %w", err)
	}

	return nil
}

func (r *UserRepository) GetProfileByID(ctx context.Context, id string) (*models.Profile, error) {
	query := `SELECT id, handle, display_name, bio, avatar_url, origin_country, has_completed_onboarding, is_official, is_private, role, created_at, COALESCE(birth_month, 0), COALESCE(birth_year, 0) FROM public.profiles WHERE id = $1::uuid`

	var p models.Profile
	err := r.pool.QueryRow(ctx, query, id).Scan(
		&p.ID, &p.Handle, &p.DisplayName, &p.Bio, &p.AvatarURL, &p.OriginCountry, &p.HasCompletedOnboarding, &p.IsOfficial, &p.IsPrivate, &p.Role, &p.CreatedAt, &p.BirthMonth, &p.BirthYear,
	)
	if err != nil {
		return nil, err
	}
	return &p, nil
}

func (r *UserRepository) GetProfileByHandle(ctx context.Context, handle string) (*models.Profile, error) {
	query := `
		SELECT id, handle, display_name, bio, avatar_url, origin_country, 
		       has_completed_onboarding, is_official, is_private, role, created_at,
		       COALESCE(birth_month, 0), COALESCE(birth_year, 0)
		FROM public.profiles 
		WHERE handle = $1
	`
	var p models.Profile
	err := r.pool.QueryRow(ctx, query, handle).Scan(
		&p.ID, &p.Handle, &p.DisplayName, &p.Bio, &p.AvatarURL, &p.OriginCountry,
		&p.HasCompletedOnboarding, &p.IsOfficial, &p.IsPrivate, &p.Role, &p.CreatedAt,
		&p.BirthMonth, &p.BirthYear,
	)
	if err != nil {
		return nil, err
	}
	return &p, nil
}

func (r *UserRepository) UpdateProfile(ctx context.Context, profile *models.Profile) error {
	query := `
		UPDATE public.profiles SET
			handle = COALESCE($1, handle),
			display_name = COALESCE($2, display_name),
			bio = COALESCE($3, bio),
			avatar_url = COALESCE($4, avatar_url),
			cover_url = COALESCE($5, cover_url),
			location = COALESCE($6, location),
			website = COALESCE($7, website),
			interests = COALESCE($8, interests),
			identity_key = COALESCE($9, identity_key),
			registration_id = COALESCE($10, registration_id),
			encrypted_private_key = COALESCE($11, encrypted_private_key),
			is_private = COALESCE($12, is_private),
			is_official = COALESCE($13, is_official),
			updated_at = NOW()
		WHERE id = $14::uuid
	`
	_, err := r.pool.Exec(ctx, query,
		profile.Handle, profile.DisplayName, profile.Bio, profile.AvatarURL,
		profile.CoverURL, profile.Location, profile.Website, profile.Interests,
		profile.IdentityKey, profile.RegistrationID, profile.EncryptedPrivateKey,
		profile.IsPrivate, profile.IsOfficial,
		profile.ID,
	)
	return err
}

func (r *UserRepository) MarkOnboardingComplete(ctx context.Context, userID string) error {
	query := `UPDATE public.profiles SET has_completed_onboarding = TRUE, updated_at = NOW() WHERE id = $1::uuid`
	_, err := r.pool.Exec(ctx, query, userID)
	return err
}

func (r *UserRepository) SearchUsers(ctx context.Context, query string, viewerID string, limit int) ([]models.Profile, error) {
	// The % operator uses pg_trgm for fuzzy matching
	sql := `
		SELECT 
			p.id, p.handle, p.display_name, p.bio, p.avatar_url, p.origin_country, p.has_completed_onboarding, p.created_at
		FROM public.profiles p
		LEFT JOIN public.trust_state t ON p.id = t.user_id
		WHERE (
			p.handle % $1 OR p.handle ILIKE '%' || $1 || '%' 
			OR p.display_name % $1 OR p.display_name ILIKE '%' || $1 || '%'
		)
		  AND (
			  p.is_private = FALSE 
			  OR ($2 != '' AND EXISTS (
				  SELECT 1 FROM public.follows f 
				  WHERE f.follower_id = $2::uuid AND f.following_id = p.id AND f.status = 'accepted'
			  ))
			  OR ($2 != '' AND p.id = $2::uuid)
		  )
		  AND NOT public.has_block_between(p.id, CASE WHEN $2 != '' THEN $2::uuid ELSE NULL END)
		ORDER BY 
			(similarity(p.handle, $1) + CASE WHEN p.handle ILIKE $1 || '%' THEN 0.5 ELSE 0 END + CASE WHEN COALESCE(t.harmony_score, 0) > 80 THEN 0.3 ELSE 0 END) DESC,
			p.created_at DESC
		LIMIT $3
	`
	rows, err := r.pool.Query(ctx, sql, query, viewerID, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var profiles []models.Profile
	for rows.Next() {
		var p models.Profile
		err := rows.Scan(
			&p.ID, &p.Handle, &p.DisplayName, &p.Bio, &p.AvatarURL, &p.OriginCountry, &p.HasCompletedOnboarding, &p.CreatedAt,
		)
		if err != nil {
			return nil, err
		}
		profiles = append(profiles, p)
	}
	return profiles, nil
}

func (r *UserRepository) GetTrustState(ctx context.Context, userID string) (*models.TrustState, error) {
	query := `SELECT user_id, harmony_score, tier, posts_today FROM public.trust_state WHERE user_id = $1::uuid`

	var ts models.TrustState
	err := r.pool.QueryRow(ctx, query, userID).Scan(
		&ts.UserID, &ts.HarmonyScore, &ts.Tier, &ts.PostsToday,
	)
	if err != nil {
		return nil, err
	}
	return &ts, nil
}

func (r *UserRepository) CreateUser(ctx context.Context, user *models.User) error {
	query := `
		INSERT INTO public.users (id, email, encrypted_password, status, mfa_enabled, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
	`
	_, err := r.pool.Exec(ctx, query, user.ID, user.Email, user.PasswordHash, user.Status, user.MFAEnabled, user.CreatedAt, user.UpdatedAt)
	if err != nil {
		return fmt.Errorf("failed to create user: %w", err)
	}
	return nil
}

func (r *UserRepository) GetUserByEmail(ctx context.Context, email string) (*models.User, error) {
	query := `SELECT id, email, encrypted_password, status, mfa_enabled, last_login, created_at FROM public.users WHERE email = $1`
	var u models.User
	err := r.pool.QueryRow(ctx, query, email).Scan(&u.ID, &u.Email, &u.PasswordHash, &u.Status, &u.MFAEnabled, &u.LastLogin, &u.CreatedAt)
	if err != nil {
		return nil, err
	}
	return &u, nil
}

func (r *UserRepository) GetUserByID(ctx context.Context, id string) (*models.User, error) {
	query := `SELECT id, email, encrypted_password, status, mfa_enabled, last_login, created_at FROM public.users WHERE id = $1::uuid`
	var u models.User
	err := r.pool.QueryRow(ctx, query, id).Scan(&u.ID, &u.Email, &u.PasswordHash, &u.Status, &u.MFAEnabled, &u.LastLogin, &u.CreatedAt)
	if err != nil {
		return nil, err
	}
	return &u, nil
}

func (r *UserRepository) FollowUser(ctx context.Context, followerID, followingID string) (string, error) {
	if followerID == followingID {
		return "", fmt.Errorf("cannot follow self")
	}

	// 1. Check if follow record already exists
	var currentStatus string
	err := r.pool.QueryRow(ctx,
		"SELECT status FROM public.follows WHERE follower_id = $1::uuid AND following_id = $2::uuid",
		followerID, followingID).Scan(&currentStatus)

	if err == nil {
		return currentStatus, nil // Return existing status if found
	}

	// 2. Check target user's privacy settings
	var isPrivate, isOfficial bool
	err = r.pool.QueryRow(ctx,
		"SELECT is_private, is_official FROM public.profiles WHERE id = $1::uuid",
		followingID).Scan(&isPrivate, &isOfficial)

	if err != nil {
		// Fallback: If profile missing, assume public (or return error)
		isPrivate = false
	}

	// 3. Determine status: Official/Public -> Accepted, Private -> Pending
	newStatus := "pending"
	if isOfficial || !isPrivate {
		newStatus = "accepted"
	}

	// 4. Insert the follow
	query := `
		INSERT INTO public.follows (follower_id, following_id, status)
		VALUES ($1::uuid, $2::uuid, $3)
		ON CONFLICT (follower_id, following_id) DO UPDATE SET status = EXCLUDED.status
		RETURNING status
	`
	var status string
	err = r.pool.QueryRow(ctx, query, followerID, followingID, newStatus).Scan(&status)

	if err != nil {
		return "", fmt.Errorf("failed to follow user: %w", err)
	}

	return status, nil
}

func (r *UserRepository) AcceptFollowRequest(ctx context.Context, userID, requesterID string) error {
	query := `
		UPDATE public.follows 
		SET status = 'accepted' 
		WHERE following_id = $1::uuid AND follower_id = $2::uuid AND status = 'pending'
	`
	commandTag, err := r.pool.Exec(ctx, query, userID, requesterID)
	if err != nil {
		return err
	}
	if commandTag.RowsAffected() == 0 {
		return fmt.Errorf("no pending request found")
	}
	return nil
}

func (r *UserRepository) GetPendingFollowRequests(ctx context.Context, userID string) ([]map[string]any, error) {
	query := `
		SELECT p.id as follower_id, p.handle, p.display_name, p.avatar_url, f.created_at as requested_at
		FROM public.follows f
		JOIN public.profiles p ON p.id = f.follower_id
		WHERE f.following_id = $1::uuid AND f.status = 'pending'
		ORDER BY f.created_at DESC
	`
	rows, err := r.pool.Query(ctx, query, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var requests []map[string]any
	for rows.Next() {
		var id, handle, displayName, avatarURL string
		var requestedAt time.Time
		if err := rows.Scan(&id, &handle, &displayName, &avatarURL, &requestedAt); err != nil {
			return nil, err
		}
		requests = append(requests, map[string]any{
			"follower_id":  id,
			"handle":       handle,
			"display_name": displayName,
			"avatar_url":   avatarURL,
			"requested_at": requestedAt,
		})
	}
	return requests, nil
}

func (r *UserRepository) UpdateHarmonyScore(ctx context.Context, userID string, delta int) error {
	query := `
		UPDATE public.trust_state 
		SET harmony_score = GREATEST(0, LEAST(harmony_score + $1, 100)),
		    updated_at = NOW(),
		    last_harmony_calc_at = NOW()
		WHERE user_id = $2::uuid
	`
	_, err := r.pool.Exec(ctx, query, delta, userID)
	return err
}

func (r *UserRepository) RejectFollowRequest(ctx context.Context, userID, requesterID string) error {
	query := `DELETE FROM public.follows WHERE follower_id = $1::uuid AND following_id = $2::uuid AND status = 'pending'`
	_, err := r.pool.Exec(ctx, query, requesterID, userID)
	return err
}

func (r *UserRepository) UnfollowUser(ctx context.Context, followerID, followingID string) error {
	query := `
		DELETE FROM public.follows WHERE follower_id = $1::uuid AND following_id = $2::uuid
	`
	_, err := r.pool.Exec(ctx, query, followerID, followingID)
	if err != nil {
		return fmt.Errorf("failed to unfollow user: %w", err)
	}
	return nil
}

func (r *UserRepository) BlockUser(ctx context.Context, blockerID, blockedID, actorIP string) error {
	tx, err := r.pool.Begin(ctx)
	if err != nil {
		return err
	}
	defer tx.Rollback(ctx)

	// Step 1: Insert Block
	query := `INSERT INTO public.blocks (blocker_id, blocked_id) VALUES ($1::uuid, $2::uuid) ON CONFLICT DO NOTHING`
	_, err = tx.Exec(ctx, query, blockerID, blockedID)
	if err != nil {
		return err
	}

	// Step 2: Log Abuse
	var handle string
	_ = tx.QueryRow(ctx, `SELECT handle FROM public.profiles WHERE id = $1::uuid`, blockedID).Scan(&handle)

	abuseQuery := `
		INSERT INTO public.abuse_logs (actor_id, blocked_id, blocked_handle, actor_ip)
		VALUES ($1::uuid, $2::uuid, $3, $4)
	`
	_, _ = tx.Exec(ctx, abuseQuery, blockerID, blockedID, handle, actorIP)

	return tx.Commit(ctx)
}

func (r *UserRepository) UnblockUser(ctx context.Context, blockerID, blockedID string) error {
	query := `DELETE FROM public.blocks WHERE blocker_id = $1::uuid AND blocked_id = $2::uuid`
	_, err := r.pool.Exec(ctx, query, blockerID, blockedID)
	return err
}

func (r *UserRepository) GetBlockedUsers(ctx context.Context, userID string) ([]models.Profile, error) {
	query := `
		SELECT p.id, p.handle, p.display_name, p.avatar_url
		FROM public.profiles p
		JOIN public.blocks b ON p.id = b.blocked_id
		WHERE b.blocker_id = $1::uuid
	`
	rows, err := r.pool.Query(ctx, query, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var profiles []models.Profile
	for rows.Next() {
		var p models.Profile
		if err := rows.Scan(&p.ID, &p.Handle, &p.DisplayName, &p.AvatarURL); err != nil {
			return nil, err
		}
		profiles = append(profiles, p)
	}
	return profiles, nil
}

func (r *UserRepository) CreateReport(ctx context.Context, report *models.Report) error {
	query := `
		INSERT INTO public.reports (reporter_id, target_user_id, post_id, comment_id, violation_type, description, status)
		VALUES ($1::uuid, $2::uuid, $3, $4, $5, $6, 'pending')
	`
	_, err := r.pool.Exec(ctx, query,
		report.ReporterID,
		report.TargetUserID,
		report.PostID,
		report.CommentID,
		report.ViolationType,
		report.Description,
	)
	return err
}

type ProfileStats struct {
	PostCount      int `json:"post_count"`
	FollowerCount  int `json:"follower_count"`
	FollowingCount int `json:"following_count"`
}

func (r *UserRepository) GetProfileStats(ctx context.Context, userID string) (*ProfileStats, error) {
	stats := &ProfileStats{}

	query := `
		SELECT
			(
				SELECT COUNT(*) FROM public.posts p
				WHERE p.author_id = $1::uuid AND p.deleted_at IS NULL
			) as post_count,
			(
				SELECT COUNT(*) FROM public.follows 
				WHERE following_id = $1::uuid AND status = 'accepted'
			) as follower_count,
			(
				SELECT COUNT(*) FROM public.follows 
				WHERE follower_id = $1::uuid AND status = 'accepted'
			) as following_count
	`
	err := r.pool.QueryRow(ctx, query, userID).Scan(
		&stats.PostCount,
		&stats.FollowerCount,
		&stats.FollowingCount,
	)
	if err != nil {
		return nil, err
	}

	return stats, nil
}

func (r *UserRepository) IsFollowing(ctx context.Context, followerID, followingID string) (bool, error) {
	var exists bool
	query := `SELECT EXISTS(SELECT 1 FROM public.follows WHERE follower_id = $1::uuid AND following_id = $2::uuid AND status = 'accepted')`
	err := r.pool.QueryRow(ctx, query, followerID, followingID).Scan(&exists)
	if err != nil {
		return false, err
	}
	return exists, nil
}

func (r *UserRepository) IsMutualFollow(ctx context.Context, userA, userB string) (bool, error) {
	if userA == userB {
		return true, nil
	}

	var exists bool
	query := `
		SELECT EXISTS (
			SELECT 1 FROM public.follows 
			WHERE follower_id = $1::uuid AND following_id = $2::uuid AND status = 'accepted'
		) AND EXISTS (
			SELECT 1 FROM public.follows 
			WHERE follower_id = $2::uuid AND following_id = $1::uuid AND status = 'accepted'
		)`
	err := r.pool.QueryRow(ctx, query, userA, userB).Scan(&exists)
	if err != nil {
		return false, err
	}
	return exists, nil
}

func (r *UserRepository) GetFollowStatus(ctx context.Context, followerID, followingID string) (string, error) {
	var status string
	query := `SELECT status FROM public.follows WHERE follower_id = $1::uuid AND following_id = $2::uuid`
	err := r.pool.QueryRow(ctx, query, followerID, followingID).Scan(&status)
	if err != nil {
		if err == pgx.ErrNoRows {
			return "", nil
		}
		return "", err
	}
	return status, nil
}

type SignalKeysInput struct {
	IdentityKeyPublic     string
	SignedPrekeyPublic    string
	SignedPrekeyID        int
	SignedPrekeySignature string
	OneTimePrekeys        []byte
	RegistrationID        int
}

func (r *UserRepository) UpsertKeys(ctx context.Context, userID string, keys SignalKeysInput) error {
	tx, err := r.pool.Begin(ctx)
	if err != nil {
		return err
	}
	defer tx.Rollback(ctx)

	// Step 1: Update identity and registration
	_, err = tx.Exec(ctx, `
		UPDATE public.profiles 
		SET identity_key = $1, registration_id = $2, updated_at = NOW()
		WHERE id = $3::uuid
	`, keys.IdentityKeyPublic, keys.RegistrationID, userID)
	if err != nil {
		return err
	}

	// Step 2: Extract and Upsert Signed Prekey
	_, err = tx.Exec(ctx, `
		INSERT INTO public.signed_prekeys (user_id, key_id, public_key, signature)
		VALUES ($1::uuid, $2, $3, $4)
		ON CONFLICT (user_id, key_id) DO UPDATE SET 
			public_key = EXCLUDED.public_key,
			signature = EXCLUDED.signature,
			created_at = NOW()
	`, userID, keys.SignedPrekeyID, keys.SignedPrekeyPublic, keys.SignedPrekeySignature)
	if err != nil {
		return err
	}

	// Step 3: Clear and Bulk Insert One-Time Prekeys
	_, err = tx.Exec(ctx, `DELETE FROM public.one_time_prekeys WHERE user_id = $1::uuid`, userID)
	if err != nil {
		return err
	}

	type OTKEntry struct {
		KeyID     int    `json:"key_id"`
		PublicKey string `json:"public_key"`
	}
	var otks []OTKEntry
	if len(keys.OneTimePrekeys) > 0 {
		if err := json.Unmarshal(keys.OneTimePrekeys, &otks); err != nil {
			return fmt.Errorf("failed to unmarshal one_time_prekeys: %w", err)
		}

		for _, otk := range otks {
			_, err = tx.Exec(ctx, `
				INSERT INTO public.one_time_prekeys (user_id, key_id, public_key)
				VALUES ($1::uuid, $2, $3)
			`, userID, otk.KeyID, otk.PublicKey)
			if err != nil {
				return err
			}
		}
	}

	return tx.Commit(ctx)
}

// DeleteUsedOTK removes a one-time prekey after it's been used for encryption
func (r *UserRepository) DeleteUsedOTK(ctx context.Context, userID string, keyID int) error {
	_, err := r.pool.Exec(ctx, `
		DELETE FROM public.one_time_prekeys 
		WHERE user_id = $1::uuid AND key_id = $2
	`, userID, keyID)
	if err != nil {
		return fmt.Errorf("failed to delete used OTK: %w", err)
	}
	// OTK deleted successfully
	return nil
}

func (r *UserRepository) GetSignalKeyBundle(ctx context.Context, userID string) (map[string]interface{}, error) {
	var ikPub, spkPub, spkSig string
	var regID sql.NullInt64
	var spkID sql.NullInt64
	err := r.pool.QueryRow(ctx, `
		SELECT 
			p.identity_key as identity_key_public,
			p.registration_id,
			sp.key_id as signed_prekey_id,
			sp.public_key as signed_prekey_public,
			sp.signature as signed_prekey_signature
		FROM public.profiles p
		LEFT JOIN public.signed_prekeys sp ON p.id = sp.user_id
		WHERE p.id = $1::uuid
	`, userID).Scan(
		&ikPub,
		&regID,
		&spkID,
		&spkPub,
		&spkSig,
	)
	if err != nil {
		return nil, fmt.Errorf("failed to get signal key bundle: %w", err)
	}

	// Get available OTKs for this user
	rows, err := r.pool.Query(ctx, `
		SELECT key_id, public_key
		FROM public.one_time_prekeys
		WHERE user_id = $1::uuid
		ORDER BY created_at ASC
		LIMIT 1
	`, userID)
	if err != nil {
		return nil, fmt.Errorf("failed to get OTKs: %w", err)
	}
	defer rows.Close()

	var otkID int
	var otkPub string
	var otk map[string]interface{}

	if rows.Next() {
		if err := rows.Scan(&otkID, &otkPub); err != nil {
			return nil, fmt.Errorf("failed to scan OTK: %w", err)
		}
		otk = map[string]interface{}{
			"key_id":     otkID,
			"public_key": otkPub,
		}
		// OTK retrieved - not logging user ID for security
	}

	// Handle NULL values properly
	var regIDValue int
	if regID.Valid {
		regIDValue = int(regID.Int64)
	} else {
		regIDValue = 1 // Default value
	}

	var spkIDValue int
	if spkID.Valid {
		spkIDValue = int(spkID.Int64)
	} else {
		spkIDValue = 1 // Default value
	}

	bundle := map[string]interface{}{
		"identity_key": map[string]interface{}{
			"key_id":     1,
			"public_key": ikPub,
		},
		"registration_id": regIDValue,
		"signed_prekey": map[string]interface{}{
			"key_id":     spkIDValue,
			"public_key": spkPub,
			"signature":  spkSig,
		},
	}

	// Add OTK if available
	if otk != nil {
		bundle["one_time_prekey"] = otk
	}

	return bundle, nil
}

func (r *UserRepository) GetPrivacySettings(ctx context.Context, userID string) (*models.PrivacySettings, error) {
	query := `
		SELECT user_id, show_location, show_interests, profile_visibility,
		       posts_visibility, saved_visibility, follow_request_policy,
		       default_post_visibility, is_private_profile, updated_at
		FROM public.profile_privacy_settings
		WHERE user_id = $1::uuid
	`
	var ps models.PrivacySettings
	err := r.pool.QueryRow(ctx, query, userID).Scan(
		&ps.UserID, &ps.ShowLocation, &ps.ShowInterests, &ps.ProfileVisibility,
		&ps.PostsVisibility, &ps.SavedVisibility, &ps.FollowRequestPolicy,
		&ps.DefaultPostVisibility, &ps.IsPrivateProfile, &ps.UpdatedAt,
	)
	if err != nil {
		if err.Error() == "no rows in result set" || err.Error() == "pgx: no rows in result set" {
			// Return default settings for new users (pointers required)
			uid, _ := uuid.Parse(userID)
			t := true
			f := false
			pub := "public"
			priv := "private"
			anyone := "everyone"
			return &models.PrivacySettings{
				UserID:                uid,
				ShowLocation:          &t,
				ShowInterests:         &t,
				ProfileVisibility:     &pub,
				PostsVisibility:       &pub,
				SavedVisibility:       &priv,
				FollowRequestPolicy:   &anyone,
				DefaultPostVisibility: &pub,
				IsPrivateProfile:      &f,
				UpdatedAt:             time.Now(),
			}, nil
		}
		return nil, err
	}
	return &ps, nil
}

func (r *UserRepository) UpdatePrivacySettings(ctx context.Context, ps *models.PrivacySettings) error {
	query := `
		INSERT INTO public.profile_privacy_settings (
			user_id, show_location, show_interests, profile_visibility,
			posts_visibility, saved_visibility, follow_request_policy,
			default_post_visibility, is_private_profile, updated_at
		) VALUES ($1::uuid, $2, $3, $4, $5, $6, $7, $8, $9, NOW())
		ON CONFLICT (user_id) DO UPDATE SET
			show_location = COALESCE(EXCLUDED.show_location, profile_privacy_settings.show_location),
			show_interests = COALESCE(EXCLUDED.show_interests, profile_privacy_settings.show_interests),
			profile_visibility = COALESCE(EXCLUDED.profile_visibility, profile_privacy_settings.profile_visibility),
			posts_visibility = COALESCE(EXCLUDED.posts_visibility, profile_privacy_settings.posts_visibility),
			saved_visibility = COALESCE(EXCLUDED.saved_visibility, profile_privacy_settings.saved_visibility),
			follow_request_policy = COALESCE(EXCLUDED.follow_request_policy, profile_privacy_settings.follow_request_policy),
			default_post_visibility = COALESCE(EXCLUDED.default_post_visibility, profile_privacy_settings.default_post_visibility),
			is_private_profile = COALESCE(EXCLUDED.is_private_profile, profile_privacy_settings.is_private_profile),
			updated_at = NOW()
	`
	_, err := r.pool.Exec(ctx, query,
		ps.UserID, ps.ShowLocation, ps.ShowInterests, ps.ProfileVisibility,
		ps.PostsVisibility, ps.SavedVisibility, ps.FollowRequestPolicy,
		ps.DefaultPostVisibility, ps.IsPrivateProfile,
	)
	return err
}

func (r *UserRepository) GetUserSettings(ctx context.Context, userID string) (*models.UserSettings, error) {
	query := `
		SELECT user_id, theme, language, notifications_enabled, email_notifications,
		       push_notifications, content_filter_level, auto_play_videos, data_saver_mode, 
		       default_post_ttl, COALESCE(nsfw_enabled, FALSE), COALESCE(nsfw_blur_enabled, TRUE), updated_at
		FROM public.user_settings
		WHERE user_id = $1::uuid
	`
	var us models.UserSettings
	err := r.pool.QueryRow(ctx, query, userID).Scan(
		&us.UserID, &us.Theme, &us.Language, &us.NotificationsEnabled, &us.EmailNotifications,
		&us.PushNotifications, &us.ContentFilterLevel, &us.AutoPlayVideos, &us.DataSaverMode,
		&us.DefaultPostTtl, &us.NSFWEnabled, &us.NSFWBlurEnabled, &us.UpdatedAt,
	)
	if err != nil {
		if err.Error() == "no rows in result set" || err.Error() == "pgx: no rows in result set" {
			// Return default settings for new users (pointers required)
			uid, _ := uuid.Parse(userID)
			sys := "system"
			en := "en"
			med := "medium"
			t := true
			f := false
			return &models.UserSettings{
				UserID:               uid,
				Theme:                &sys,
				Language:             &en,
				NotificationsEnabled: &t,
				EmailNotifications:   &t,
				PushNotifications:    &t,
				ContentFilterLevel:   &med,
				AutoPlayVideos:       &t,
				DataSaverMode:        &f,
				NSFWEnabled:          &f,
				NSFWBlurEnabled:      &t,
				UpdatedAt:            time.Now(),
			}, nil
		}
		return nil, err
	}
	return &us, nil
}

func (r *UserRepository) UpdateUserSettings(ctx context.Context, us *models.UserSettings) error {
	query := `
		INSERT INTO public.user_settings (
			user_id, theme, language, notifications_enabled, email_notifications,
			push_notifications, content_filter_level, auto_play_videos, data_saver_mode, 
			default_post_ttl, nsfw_enabled, nsfw_blur_enabled, updated_at
		) VALUES ($1::uuid, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, NOW())
		ON CONFLICT (user_id) DO UPDATE SET
			theme = COALESCE(EXCLUDED.theme, user_settings.theme),
			language = COALESCE(EXCLUDED.language, user_settings.language),
			notifications_enabled = COALESCE(EXCLUDED.notifications_enabled, user_settings.notifications_enabled),
			email_notifications = COALESCE(EXCLUDED.email_notifications, user_settings.email_notifications),
			push_notifications = COALESCE(EXCLUDED.push_notifications, user_settings.push_notifications),
			content_filter_level = COALESCE(EXCLUDED.content_filter_level, user_settings.content_filter_level),
			auto_play_videos = COALESCE(EXCLUDED.auto_play_videos, user_settings.auto_play_videos),
			data_saver_mode = COALESCE(EXCLUDED.data_saver_mode, user_settings.data_saver_mode),
			default_post_ttl = COALESCE(EXCLUDED.default_post_ttl, user_settings.default_post_ttl),
			nsfw_enabled = COALESCE(EXCLUDED.nsfw_enabled, user_settings.nsfw_enabled),
			nsfw_blur_enabled = COALESCE(EXCLUDED.nsfw_blur_enabled, user_settings.nsfw_blur_enabled),
			updated_at = NOW()
	`
	_, err := r.pool.Exec(ctx, query,
		us.UserID, us.Theme, us.Language, us.NotificationsEnabled, us.EmailNotifications,
		us.PushNotifications, us.ContentFilterLevel, us.AutoPlayVideos, us.DataSaverMode,
		us.DefaultPostTtl, us.NSFWEnabled, us.NSFWBlurEnabled,
	)
	return err
}

func (r *UserRepository) UpsertFCMToken(ctx context.Context, userID string, token string, platform string) error {
	query := `
		INSERT INTO public.user_fcm_tokens (user_id, token, device_type, last_updated)
		VALUES ($1::uuid, $2, $3, NOW())
		ON CONFLICT (user_id, token) DO UPDATE SET
			last_updated = NOW(),
			device_type = EXCLUDED.device_type
	`
	_, err := r.pool.Exec(ctx, query, userID, token, platform)
	return err
}

func (r *UserRepository) GetFCMTokens(ctx context.Context, userID string) ([]string, error) {
	query := `SELECT token FROM public.user_fcm_tokens WHERE user_id = $1::uuid`
	rows, err := r.pool.Query(ctx, query, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var tokens []string
	for rows.Next() {
		var token string
		if err := rows.Scan(&token); err != nil {
			return nil, err
		}
		tokens = append(tokens, token)
	}
	return tokens, nil
}

func (r *UserRepository) DeleteFCMToken(ctx context.Context, userID string, token string) error {
	_, err := r.pool.Exec(ctx, `
		DELETE FROM public.user_fcm_tokens
		WHERE user_id = $1::uuid AND token = $2
	`, userID, token)
	return err
}

func (r *UserRepository) StoreRefreshToken(ctx context.Context, userID string, tokenString string, duration time.Duration) error {
	hash := sha256.Sum256([]byte(tokenString))
	hashString := hex.EncodeToString(hash[:])

	query := `
		INSERT INTO refresh_tokens (token_hash, user_id, expires_at)
		VALUES ($1, $2::uuid, $3)
	`
	_, err := r.pool.Exec(ctx, query, hashString, userID, time.Now().Add(duration))
	return err
}

func (r *UserRepository) ValidateRefreshToken(ctx context.Context, tokenString string) (*models.RefreshToken, error) {
	hash := sha256.Sum256([]byte(tokenString))
	hashString := hex.EncodeToString(hash[:])

	var rt models.RefreshToken
	query := `
		SELECT token_hash, user_id, expires_at, revoked, created_at
		FROM refresh_tokens
		WHERE token_hash = $1
	`
	err := r.pool.QueryRow(ctx, query, hashString).Scan(
		&rt.TokenHash, &rt.UserID, &rt.ExpiresAt, &rt.Revoked, &rt.CreatedAt,
	)
	if err != nil {
		return nil, err
	}

	if rt.Revoked {
		return nil, fmt.Errorf("token revoked")
	}
	if time.Now().After(rt.ExpiresAt) {
		return nil, fmt.Errorf("token expired")
	}

	return &rt, nil
}

func (r *UserRepository) RevokeRefreshToken(ctx context.Context, tokenString string) error {
	hash := sha256.Sum256([]byte(tokenString))
	hashString := hex.EncodeToString(hash[:])

	query := `UPDATE refresh_tokens SET revoked = true WHERE token_hash = $1`
	_, err := r.pool.Exec(ctx, query, hashString)
	return err
}

func (r *UserRepository) RevokeAllUserTokens(ctx context.Context, userID string) error {
	query := `UPDATE refresh_tokens SET revoked = true WHERE user_id = $1::uuid`
	_, err := r.pool.Exec(ctx, query, userID)
	return err
}

func (r *UserRepository) CreateAuthToken(ctx context.Context, token *models.AuthToken) error {
	query := `INSERT INTO public.auth_tokens (token, user_id, type, expires_at, created_at) VALUES ($1, $2, $3, $4, $5)`
	_, err := r.pool.Exec(ctx, query, token.Token, token.UserID, token.Type, token.ExpiresAt, token.CreatedAt)
	return err
}

func (r *UserRepository) GetAuthToken(ctx context.Context, tokenStr string) (*models.AuthToken, error) {
	query := `SELECT token, user_id, type, expires_at, created_at FROM public.auth_tokens WHERE token = $1`
	var t models.AuthToken
	err := r.pool.QueryRow(ctx, query, tokenStr).Scan(&t.Token, &t.UserID, &t.Type, &t.ExpiresAt, &t.CreatedAt)
	if err != nil {
		return nil, err
	}
	return &t, nil
}

func (r *UserRepository) DeleteAuthToken(ctx context.Context, tokenStr string) error {
	_, err := r.pool.Exec(ctx, `DELETE FROM public.auth_tokens WHERE token = $1`, tokenStr)
	return err
}

func (r *UserRepository) UpdateUserStatus(ctx context.Context, userID string, status models.UserStatus) error {
	_, err := r.pool.Exec(ctx, `UPDATE public.users SET status = $1, updated_at = NOW() WHERE id = $2::uuid`, status, userID)
	return err
}

func (r *UserRepository) UpdateLastLogin(ctx context.Context, userID string) error {
	_, err := r.pool.Exec(ctx, `UPDATE public.users SET last_login = NOW() WHERE id = $1::uuid`, userID)
	return err
}

func (r *UserRepository) UpsertMFASecret(ctx context.Context, secret *models.MFASecret) error {
	query := `
		INSERT INTO public.user_mfa_secrets (user_id, secret_key, recovery_codes, updated_at)
		VALUES ($1, $2, $3, NOW())
		ON CONFLICT (user_id) DO UPDATE SET
			secret_key = EXCLUDED.secret_key,
			recovery_codes = EXCLUDED.recovery_codes,
			updated_at = NOW()
	`
	_, err := r.pool.Exec(ctx, query, secret.UserID, secret.Secret, secret.RecoveryCodes)
	return err
}

func (r *UserRepository) GetMFASecret(ctx context.Context, userID string) (*models.MFASecret, error) {
	query := `SELECT user_id, secret_key, recovery_codes FROM public.user_mfa_secrets WHERE user_id = $1::uuid`
	var s models.MFASecret
	err := r.pool.QueryRow(ctx, query, userID).Scan(&s.UserID, &s.Secret, &s.RecoveryCodes)
	if err != nil {
		return nil, err
	}
	return &s, nil
}

// Verification Tokens (New Table)
func (r *UserRepository) CreateVerificationToken(ctx context.Context, tokenHash string, userID string, duration time.Duration) error {
	query := `INSERT INTO public.verification_tokens (token_hash, user_id, expires_at) VALUES ($1, $2, $3)`
	_, err := r.pool.Exec(ctx, query, tokenHash, userID, time.Now().Add(duration))
	return err
}

func (r *UserRepository) GetVerificationToken(ctx context.Context, tokenHash string) (string, time.Time, error) {
	query := `SELECT user_id, expires_at FROM public.verification_tokens WHERE token_hash = $1`
	var userID string
	var expiresAt time.Time
	err := r.pool.QueryRow(ctx, query, tokenHash).Scan(&userID, &expiresAt)
	if err != nil {
		return "", time.Time{}, err
	}
	return userID, expiresAt, nil
}

func (r *UserRepository) DeleteVerificationToken(ctx context.Context, tokenHash string) error {
	query := `DELETE FROM public.verification_tokens WHERE token_hash = $1`
	_, err := r.pool.Exec(ctx, query, tokenHash)
	return err
}

// Password Reset Tokens
func (r *UserRepository) CreatePasswordResetToken(ctx context.Context, tokenHash string, userID string, duration time.Duration) error {
	query := `INSERT INTO public.password_reset_tokens (token_hash, user_id, expires_at) VALUES ($1, $2, $3)`
	_, err := r.pool.Exec(ctx, query, tokenHash, userID, time.Now().Add(duration))
	return err
}

func (r *UserRepository) GetPasswordResetToken(ctx context.Context, tokenHash string) (string, time.Time, error) {
	query := `SELECT user_id, expires_at FROM public.password_reset_tokens WHERE token_hash = $1`
	var userID string
	var expiresAt time.Time
	err := r.pool.QueryRow(ctx, query, tokenHash).Scan(&userID, &expiresAt)
	if err != nil {
		return "", time.Time{}, err
	}
	return userID, expiresAt, nil
}

func (r *UserRepository) DeletePasswordResetToken(ctx context.Context, tokenHash string) error {
	query := `DELETE FROM public.password_reset_tokens WHERE token_hash = $1`
	_, err := r.pool.Exec(ctx, query, tokenHash)
	return err
}

func (r *UserRepository) UpdateUserPassword(ctx context.Context, userID string, passwordHash string) error {
	query := `UPDATE public.users SET encrypted_password = $1, updated_at = NOW() WHERE id = $2::uuid`
	_, err := r.pool.Exec(ctx, query, passwordHash, userID)
	return err
}

// WebAuthn Credentials (placeholder comment if more methods follow, or strictly implement the requested method)

func (r *UserRepository) FetchAndConsumeOneTimeKey(ctx context.Context, userID string) (*models.OneTimePrekey, error) {
	tx, err := r.pool.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	var otk models.OneTimePrekey
	// Logic: Execute SELECT ... FROM one_time_prekeys WHERE user_id = $1 ORDER BY created_at ASC LIMIT 1 FOR UPDATE SKIP LOCKED
	query := `
		SELECT key_id, public_key
		FROM public.one_time_prekeys
		WHERE user_id = $1::uuid
		ORDER BY created_at ASC
		LIMIT 1
		FOR UPDATE SKIP LOCKED
	`

	err = tx.QueryRow(ctx, query, userID).Scan(&otk.KeyID, &otk.PublicKey)
	if err != nil {
		if err == pgx.ErrNoRows {
			// If not found: Return nil (this is valid protocol behavior)
			return nil, tx.Commit(ctx)
		}
		return nil, err
	}

	// If found: DELETE the row immediately and return the key data.
	_, err = tx.Exec(ctx, `DELETE FROM public.one_time_prekeys WHERE user_id = $1::uuid AND key_id = $2`, userID, otk.KeyID)
	if err != nil {
		return nil, fmt.Errorf("failed to delete one-time key: %w", err)
	}

	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}

	return &otk, nil
}
func (r *UserRepository) CreateWebAuthnCredential(ctx context.Context, cred *models.WebAuthnCredential) error {
	query := `
		INSERT INTO public.webauthn_credentials (user_id, credential_id, public_key, attestation_type, aaguid, sign_count, last_used_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
	`
	_, err := r.pool.Exec(ctx, query, cred.UserID, cred.ID, cred.PublicKey, cred.AttestationType, cred.AAGUID, cred.SignCount, cred.LastUsedAt)
	return err
}

func (r *UserRepository) GetWebAuthnCredentials(ctx context.Context, userID string) ([]models.WebAuthnCredential, error) {
	query := `SELECT id, user_id, credential_id, public_key, attestation_type, aaguid, sign_count, last_used_at FROM public.webauthn_credentials WHERE user_id = $1::uuid`
	rows, err := r.pool.Query(ctx, query, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var creds []models.WebAuthnCredential
	for rows.Next() {
		var c models.WebAuthnCredential
		var idStr string
		err := rows.Scan(&idStr, &c.UserID, &c.ID, &c.PublicKey, &c.AttestationType, &c.AAGUID, &c.SignCount, &c.LastUsedAt)
		if err != nil {
			return nil, err
		}
		creds = append(creds, c)
	}
	return creds, nil
}

// DeleteUser removes a user by ID. SHOULD BE USED WITH CAUTION (e.g. rollback)
func (r *UserRepository) DeleteUser(ctx context.Context, userID uuid.UUID) error {
	query := `DELETE FROM public.users WHERE id = $1`
	_, err := r.pool.Exec(ctx, query, userID)
	return err
}
func (r *UserRepository) BlockUserByHandle(ctx context.Context, actorID string, handle string, actorIP string) error {
	var targetID uuid.UUID
	err := r.pool.QueryRow(ctx, "SELECT id FROM public.profiles WHERE handle = $1", handle).Scan(&targetID)
	if err != nil {
		return err
	}
	return r.BlockUser(ctx, actorID, targetID.String(), actorIP)
}

// ========================================================================
// Social Graph: Followers & Following Lists
// ========================================================================

type FollowerUser struct {
	ID           uuid.UUID `json:"id"`
	Handle       string    `json:"handle"`
	DisplayName  string    `json:"display_name"`
	AvatarURL    *string   `json:"avatar_url"`
	HarmonyScore int       `json:"harmony_score"`
	Tier         string    `json:"tier"`
	FollowedAt   time.Time `json:"followed_at"`
}

// GetFollowers returns a list of users following the specified user
func (r *UserRepository) GetFollowers(ctx context.Context, userID string, limit, offset int) ([]FollowerUser, error) {
	query := `
		SELECT 
			p.id, p.handle, p.display_name, p.avatar_url,
			COALESCE(t.harmony_score, 50) as harmony_score,
			COALESCE(t.tier, 'new') as tier,
			f.created_at as followed_at
		FROM public.follows f
		JOIN public.profiles p ON p.id = f.follower_id
		LEFT JOIN public.trust_state t ON t.user_id = p.id
		WHERE f.following_id = $1::uuid AND f.status = 'accepted'
		ORDER BY f.created_at DESC
		LIMIT $2 OFFSET $3
	`
	rows, err := r.pool.Query(ctx, query, userID, limit, offset)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var followers []FollowerUser
	for rows.Next() {
		var f FollowerUser
		if err := rows.Scan(&f.ID, &f.Handle, &f.DisplayName, &f.AvatarURL, &f.HarmonyScore, &f.Tier, &f.FollowedAt); err != nil {
			return nil, err
		}
		followers = append(followers, f)
	}
	return followers, nil
}

// GetFollowing returns a list of users the specified user is following
func (r *UserRepository) GetFollowing(ctx context.Context, userID string, limit, offset int) ([]FollowerUser, error) {
	query := `
		SELECT 
			p.id, p.handle, p.display_name, p.avatar_url,
			COALESCE(t.harmony_score, 50) as harmony_score,
			COALESCE(t.tier, 'new') as tier,
			f.created_at as followed_at
		FROM public.follows f
		JOIN public.profiles p ON p.id = f.following_id
		LEFT JOIN public.trust_state t ON t.user_id = p.id
		WHERE f.follower_id = $1::uuid AND f.status = 'accepted'
		ORDER BY f.created_at DESC
		LIMIT $2 OFFSET $3
	`
	rows, err := r.pool.Query(ctx, query, userID, limit, offset)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var following []FollowerUser
	for rows.Next() {
		var f FollowerUser
		if err := rows.Scan(&f.ID, &f.Handle, &f.DisplayName, &f.AvatarURL, &f.HarmonyScore, &f.Tier, &f.FollowedAt); err != nil {
			return nil, err
		}
		following = append(following, f)
	}
	return following, nil
}

// ========================================================================
// Circle (Close Friends) Management
// ========================================================================

// AddToCircle adds a user to the current user's circle
func (r *UserRepository) AddToCircle(ctx context.Context, userID, memberID string) error {
	// Verify that the user follows the member first
	isFollowing, err := r.IsFollowing(ctx, userID, memberID)
	if err != nil {
		return err
	}
	if !isFollowing {
		return fmt.Errorf("can only add users you follow to your circle")
	}

	query := `
		INSERT INTO public.circle_members (user_id, member_id)
		VALUES ($1::uuid, $2::uuid)
		ON CONFLICT DO NOTHING
	`
	_, err = r.pool.Exec(ctx, query, userID, memberID)
	return err
}

// RemoveFromCircle removes a user from the current user's circle
func (r *UserRepository) RemoveFromCircle(ctx context.Context, userID, memberID string) error {
	query := `DELETE FROM public.circle_members WHERE user_id = $1::uuid AND member_id = $2::uuid`
	_, err := r.pool.Exec(ctx, query, userID, memberID)
	return err
}

// GetCircleMembers returns all users in the current user's circle
func (r *UserRepository) GetCircleMembers(ctx context.Context, userID string) ([]FollowerUser, error) {
	query := `
		SELECT 
			p.id, p.handle, p.display_name, p.avatar_url,
			COALESCE(t.harmony_score, 50) as harmony_score,
			COALESCE(t.tier, 'new') as tier,
			c.created_at as followed_at
		FROM public.circle_members c
		JOIN public.profiles p ON p.id = c.member_id
		LEFT JOIN public.trust_state t ON t.user_id = p.id
		WHERE c.user_id = $1::uuid
		ORDER BY c.created_at DESC
	`
	rows, err := r.pool.Query(ctx, query, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var members []FollowerUser
	for rows.Next() {
		var m FollowerUser
		if err := rows.Scan(&m.ID, &m.Handle, &m.DisplayName, &m.AvatarURL, &m.HarmonyScore, &m.Tier, &m.FollowedAt); err != nil {
			return nil, err
		}
		members = append(members, m)
	}
	return members, nil
}

// IsInCircle checks if a user is in another user's circle
func (r *UserRepository) IsInCircle(ctx context.Context, ownerID, userID string) (bool, error) {
	var exists bool
	query := `SELECT EXISTS(SELECT 1 FROM public.circle_members WHERE user_id = $1::uuid AND member_id = $2::uuid)`
	err := r.pool.QueryRow(ctx, query, ownerID, userID).Scan(&exists)
	return exists, err
}

// ========================================================================
// Data Export (Portability)
// ========================================================================

type UserExportData struct {
	Profile   *models.Profile  `json:"profile"`
	Posts     []ExportedPost   `json:"posts"`
	Following []ExportedFollow `json:"following"`
}

type ExportedPost struct {
	ID        uuid.UUID `json:"id"`
	Body      string    `json:"body"`
	ImageURL  *string   `json:"image_url,omitempty"`
	VideoURL  *string   `json:"video_url,omitempty"`
	CreatedAt time.Time `json:"created_at"`
}

type ExportedFollow struct {
	Handle      string    `json:"handle"`
	DisplayName string    `json:"display_name"`
	FollowedAt  time.Time `json:"followed_at"`
}

// ExportUserData generates complete user data export for portability
func (r *UserRepository) ExportUserData(ctx context.Context, userID string) (*UserExportData, error) {
	export := &UserExportData{}

	// 1. Get Profile
	profile, err := r.GetProfileByID(ctx, userID)
	if err != nil {
		return nil, fmt.Errorf("failed to get profile: %w", err)
	}
	export.Profile = profile

	// 2. Get All Posts
	postQuery := `
		SELECT id, body, image_url, video_url, created_at
		FROM public.posts
		WHERE author_id = $1::uuid AND deleted_at IS NULL
		ORDER BY created_at DESC
	`
	rows, err := r.pool.Query(ctx, postQuery, userID)
	if err != nil {
		return nil, fmt.Errorf("failed to get posts: %w", err)
	}
	defer rows.Close()

	for rows.Next() {
		var p ExportedPost
		if err := rows.Scan(&p.ID, &p.Body, &p.ImageURL, &p.VideoURL, &p.CreatedAt); err != nil {
			return nil, err
		}
		export.Posts = append(export.Posts, p)
	}

	// 3. Get Following List
	followQuery := `
		SELECT p.handle, p.display_name, f.created_at
		FROM public.follows f
		JOIN public.profiles p ON p.id = f.following_id
		WHERE f.follower_id = $1::uuid AND f.status = 'accepted'
		ORDER BY f.created_at DESC
	`
	rows, err = r.pool.Query(ctx, followQuery, userID)
	if err != nil {
		return nil, fmt.Errorf("failed to get following list: %w", err)
	}
	defer rows.Close()

	for rows.Next() {
		var f ExportedFollow
		if err := rows.Scan(&f.Handle, &f.DisplayName, &f.FollowedAt); err != nil {
			return nil, err
		}
		export.Following = append(export.Following, f)
	}

	return export, nil
}

// BanIP records an IP address as banned (used when a user is banned to prevent evasion)
func (r *UserRepository) BanIP(ctx context.Context, ipAddress string, userID string, reason string) error {
	_, err := r.pool.Exec(ctx, `
		INSERT INTO banned_ips (ip_address, user_id, reason, banned_at)
		VALUES ($1, $2::uuid, $3, NOW())
	`, ipAddress, userID, reason)
	return err
}

// IsIPBanned checks if an IP address has been banned
func (r *UserRepository) IsIPBanned(ctx context.Context, ipAddress string) (bool, error) {
	var exists bool
	err := r.pool.QueryRow(ctx, `
		SELECT EXISTS(SELECT 1 FROM banned_ips WHERE ip_address = $1)
	`, ipAddress).Scan(&exists)
	return exists, err
}

// ========================================================================
// Account Lifecycle: Deactivate, Delete, Destroy
// ========================================================================

// DeactivateUser sets user status to deactivated, preserves all data
func (r *UserRepository) DeactivateUser(ctx context.Context, userID string) error {
	_, err := r.pool.Exec(ctx, `
		UPDATE public.users 
		SET status = 'deactivated', updated_at = NOW() 
		WHERE id = $1::uuid
	`, userID)
	return err
}

// ReactivateUser sets a deactivated user back to active
func (r *UserRepository) ReactivateUser(ctx context.Context, userID string) error {
	_, err := r.pool.Exec(ctx, `
		UPDATE public.users 
		SET status = 'active', deleted_at = NULL, updated_at = NOW() 
		WHERE id = $1::uuid AND status IN ('deactivated', 'pending_deletion')
	`, userID)
	return err
}

// ScheduleDeletion marks account for deletion after 14 days
func (r *UserRepository) ScheduleDeletion(ctx context.Context, userID string) error {
	_, err := r.pool.Exec(ctx, `
		UPDATE public.users 
		SET status = 'pending_deletion', deleted_at = NOW() + INTERVAL '14 days', updated_at = NOW() 
		WHERE id = $1::uuid
	`, userID)
	return err
}

// CancelDeletion reverts a pending deletion back to active
func (r *UserRepository) CancelDeletion(ctx context.Context, userID string) error {
	return r.ReactivateUser(ctx, userID)
}

// GetAccountsPendingPurge returns user IDs whose deletion grace period has expired
func (r *UserRepository) GetAccountsPendingPurge(ctx context.Context) ([]string, error) {
	rows, err := r.pool.Query(ctx, `
		SELECT id::text FROM public.users 
		WHERE status = 'pending_deletion' AND deleted_at IS NOT NULL AND deleted_at <= NOW()
	`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var ids []string
	for rows.Next() {
		var id string
		if err := rows.Scan(&id); err != nil {
			return nil, err
		}
		ids = append(ids, id)
	}
	return ids, nil
}

// CascadePurgeUser permanently deletes ALL user data from every table. Irreversible.
func (r *UserRepository) CascadePurgeUser(ctx context.Context, userID string) error {
	tx, err := r.pool.Begin(ctx)
	if err != nil {
		return fmt.Errorf("begin tx: %w", err)
	}
	defer tx.Rollback(ctx)

	// Order matters: delete from leaf tables first, then parent tables
	purgeQueries := []string{
		// Post-related (leaf tables first)
		`DELETE FROM public.post_reactions WHERE post_id IN (SELECT id FROM public.posts WHERE author_id = $1::uuid)`,
		`DELETE FROM public.post_likes WHERE post_id IN (SELECT id FROM public.posts WHERE author_id = $1::uuid)`,
		`DELETE FROM public.post_saves WHERE post_id IN (SELECT id FROM public.posts WHERE author_id = $1::uuid)`,
		`DELETE FROM public.post_mentions WHERE post_id IN (SELECT id FROM public.posts WHERE author_id = $1::uuid)`,
		`DELETE FROM public.post_interactions WHERE post_id IN (SELECT id FROM public.posts WHERE author_id = $1::uuid)`,
		`DELETE FROM public.post_metrics WHERE post_id IN (SELECT id FROM public.posts WHERE author_id = $1::uuid)`,
		`DELETE FROM public.post_hashtags WHERE post_id IN (SELECT id FROM public.posts WHERE author_id = $1::uuid)`,
		`DELETE FROM public.post_categories WHERE post_id IN (SELECT id FROM public.posts WHERE author_id = $1::uuid)`,
		// Also remove this user's likes/saves/reactions on OTHER people's posts
		`DELETE FROM public.post_reactions WHERE user_id = $1::uuid`,
		`DELETE FROM public.post_likes WHERE user_id = $1::uuid`,
		`DELETE FROM public.post_saves WHERE user_id = $1::uuid`,
		`DELETE FROM public.post_interactions WHERE user_id = $1::uuid`,
		// Beacon votes
		`DELETE FROM public.beacon_votes WHERE user_id = $1::uuid`,
		// Comments on user's posts
		`DELETE FROM public.comments WHERE post_id IN (SELECT id FROM public.posts WHERE author_id = $1::uuid)`,
		// User's own comments
		`DELETE FROM public.comments WHERE author_id = $1::uuid`,
		// Feed engagement
		`DELETE FROM public.feed_engagement WHERE user_id = $1::uuid`,
		// Posts themselves
		`DELETE FROM public.posts WHERE author_id = $1::uuid`,
		// Messaging / E2EE
		`DELETE FROM public.secure_messages WHERE sender_id = $1::uuid OR receiver_id = $1::uuid`,
		`DELETE FROM public.encrypted_messages WHERE sender_id = $1::uuid OR receiver_id = $1::uuid`,
		`DELETE FROM public.e2ee_session_state WHERE session_id IN (SELECT id FROM public.e2ee_sessions WHERE user_a = $1::uuid OR user_b = $1::uuid)`,
		`DELETE FROM public.e2ee_sessions WHERE user_a = $1::uuid OR user_b = $1::uuid`,
		`DELETE FROM public.encrypted_conversations WHERE participant_a = $1::uuid OR participant_b = $1::uuid`,
		// Signal keys
		`DELETE FROM public.one_time_prekeys WHERE user_id = $1::uuid`,
		`DELETE FROM public.signed_prekeys WHERE user_id = $1::uuid`,
		`DELETE FROM public.signal_keys WHERE user_id = $1::uuid`,
		// Social graph
		`DELETE FROM public.circle_members WHERE user_id = $1::uuid OR member_id = $1::uuid`,
		`DELETE FROM public.follows WHERE follower_id = $1::uuid OR following_id = $1::uuid`,
		`DELETE FROM public.blocks WHERE blocker_id = $1::uuid OR blocked_id = $1::uuid`,
		// Notifications
		`DELETE FROM public.notifications WHERE user_id = $1::uuid OR actor_id = $1::uuid`,
		`DELETE FROM public.notification_preferences WHERE user_id = $1::uuid`,
		`DELETE FROM public.user_fcm_tokens WHERE user_id = $1::uuid`,
		// Moderation & violations
		`DELETE FROM public.moderation_flags WHERE user_id = $1::uuid`,
		`DELETE FROM public.pending_moderation WHERE user_id = $1::uuid`,
		`DELETE FROM public.user_violation_history WHERE user_id = $1::uuid`,
		`DELETE FROM public.user_violations WHERE user_id = $1::uuid`,
		`DELETE FROM public.user_appeals WHERE user_id = $1::uuid`,
		`DELETE FROM public.content_strikes WHERE user_id = $1::uuid`,
		`DELETE FROM public.reports WHERE reporter_id = $1::uuid`,
		`DELETE FROM public.abuse_logs WHERE user_id = $1::uuid`,
		`DELETE FROM public.user_status_history WHERE user_id = $1::uuid`,
		// Categories & hashtags
		`DELETE FROM public.user_category_preferences WHERE user_id = $1::uuid`,
		`DELETE FROM public.user_category_settings WHERE user_id = $1::uuid`,
		`DELETE FROM public.hashtag_follows WHERE user_id = $1::uuid`,
		// Backup & recovery
		`DELETE FROM public.recovery_shard_submissions WHERE session_id IN (SELECT id FROM public.recovery_sessions WHERE user_id = $1::uuid)`,
		`DELETE FROM public.recovery_sessions WHERE user_id = $1::uuid`,
		`DELETE FROM public.recovery_guardians WHERE user_id = $1::uuid OR guardian_id = $1::uuid`,
		`DELETE FROM public.cloud_backups WHERE user_id = $1::uuid`,
		`DELETE FROM public.backup_preferences WHERE user_id = $1::uuid`,
		`DELETE FROM public.sync_codes WHERE user_id = $1::uuid`,
		`DELETE FROM public.user_devices WHERE user_id = $1::uuid`,
		// Auth & tokens
		`DELETE FROM public.auth_tokens WHERE user_id = $1::uuid`,
		`DELETE FROM public.refresh_tokens WHERE user_id = $1::uuid`,
		`DELETE FROM public.verification_tokens WHERE user_id = $1::uuid`,
		`DELETE FROM public.password_reset_tokens WHERE user_id = $1::uuid`,
		`DELETE FROM public.webauthn_credentials WHERE user_id = $1::uuid`,
		`DELETE FROM public.user_mfa_secrets WHERE user_id = $1::uuid`,
		// Settings
		`DELETE FROM public.user_settings WHERE user_id = $1::uuid`,
		`DELETE FROM public.profile_privacy_settings WHERE user_id = $1::uuid`,
		// Trust
		`DELETE FROM public.trust_state WHERE user_id = $1::uuid`,
		// Username claims
		`DELETE FROM public.username_claim_requests WHERE user_id = $1::uuid`,
		// Profile & user (last)
		`DELETE FROM public.profiles WHERE id = $1::uuid`,
		`DELETE FROM public.users WHERE id = $1::uuid`,
	}

	for _, q := range purgeQueries {
		if _, err := tx.Exec(ctx, q, userID); err != nil {
			return fmt.Errorf("purge query failed: %s: %w", q[:60], err)
		}
	}

	return tx.Commit(ctx)
}
