<?php

declare(strict_types=1);

namespace SecurWP\Modules\Scanner;

use SecurWP\Modules\Scanner\Repository\IssueRepository;

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class PrivilegedAccountInspector {
    private const CAP_ROW_BATCH_SIZE = 300;
    private const EVENT_OPTION = 'securwp_privileged_account_events';
    private const EVENT_SCAN_LIMIT = 300;
    private const HIDDEN_CANDIDATE_BATCH_SIZE = 60;
    private const HIDDEN_VISIBLE_PAGE_SIZE = 100;
    private const HIDDEN_MAX_VISIBLE_PAGES_PER_SLICE = 8;
    private const MANAGEMENT_CAP = 'manage_options';

    private array $events_by_target = [];

    public function inspect( array $state, int $start_time, int $time_limit_seconds ): array {
        $state = $this->normalize_state( $state );
        $issues = [];
        $this->events_by_target = $this->load_event_index();

        if ( ! $state['network_checked'] ) {
            $issues = array_merge( $issues, $this->inspect_network_super_admins( $start_time, $time_limit_seconds ) );
            $state['network_checked'] = true;
            if ( $this->time_exceeded( $start_time, $time_limit_seconds ) ) {
                return [ 'issues' => $issues, 'complete' => false, 'state' => $state ];
            }
        }

        while ( ! $this->time_exceeded( $start_time, $time_limit_seconds ) ) {
            $site_id = $state['site_id'] > 0 ? $state['site_id'] : $this->get_site_id_at_offset( $state['site_offset'] );
            if ( $site_id <= 0 ) {
                return [ 'issues' => $issues, 'complete' => true, 'state' => $this->completed_state() ];
            }

            $state['site_id'] = $site_id;
            $context = $this->build_site_context( $site_id );

            if ( ! $state['hidden_checked'] ) {
                $hidden_result = $this->inspect_hidden_privileged_accounts( $context, $state, $start_time, $time_limit_seconds );
                if ( ! empty( $hidden_result['issues'] ) && is_array( $hidden_result['issues'] ) ) {
                    $issues = array_merge( $issues, $hidden_result['issues'] );
                }
                $state = isset( $hidden_result['state'] ) && is_array( $hidden_result['state'] )
                    ? $hidden_result['state']
                    : $state;

                if ( empty( $hidden_result['complete'] ) ) {
                    return [ 'issues' => $issues, 'complete' => false, 'state' => $state ];
                }
            }

            $rows = $this->fetch_capability_rows( $context['cap_key'], $state['last_umeta_id'], self::CAP_ROW_BATCH_SIZE );
            foreach ( $rows as $row ) {
                if ( $this->time_exceeded( $start_time, $time_limit_seconds ) ) {
                    return [ 'issues' => $issues, 'complete' => false, 'state' => $state ];
                }

                $issues = array_merge( $issues, $this->inspect_capability_row( $context, $row ) );
                $state['last_umeta_id'] = max( $state['last_umeta_id'], (int) ( $row['umeta_id'] ?? 0 ) );
            }

            if ( count( $rows ) >= self::CAP_ROW_BATCH_SIZE ) {
                return [ 'issues' => $issues, 'complete' => false, 'state' => $state ];
            }

            $state['site_offset']++;
            $state['site_id'] = 0;
            $state['last_umeta_id'] = 0;
            $state['hidden_checked'] = false;
            $state['hidden_before_umeta_id'] = 0;
            $state['hidden_batch'] = [];
        }

        return [ 'issues' => $issues, 'complete' => false, 'state' => $state ];
    }

    private function normalize_state( array $state ): array {
        return [
            'network_checked'    => ! empty( $state['network_checked'] ),
            'site_offset'        => max( 0, (int) ( $state['site_offset'] ?? 0 ) ),
            'site_id'            => max( 0, (int) ( $state['site_id'] ?? 0 ) ),
            'last_umeta_id'      => max( 0, (int) ( $state['last_umeta_id'] ?? 0 ) ),
            'hidden_checked'     => ! empty( $state['hidden_checked'] ),
            'hidden_before_umeta_id' => max( 0, (int) ( $state['hidden_before_umeta_id'] ?? 0 ) ),
            'hidden_batch'       => $this->normalize_hidden_batch( $state['hidden_batch'] ?? [] ),
        ];
    }

    private function completed_state(): array {
        return [
            'network_checked'    => true,
            'site_offset'        => 0,
            'site_id'            => 0,
            'last_umeta_id'      => 0,
            'hidden_checked'     => false,
            'hidden_before_umeta_id' => 0,
            'hidden_batch'       => [],
        ];
    }

    private function time_exceeded( int $start_time, int $time_limit_seconds ): bool {
        $limit = max( 2, $time_limit_seconds );
        return ( time() - $start_time ) >= max( 1, $limit - 1 );
    }

    private function get_site_id_at_offset( int $offset ): int {
        if ( ! is_multisite() || ! function_exists( 'get_sites' ) ) {
            return $offset === 0 ? (int) get_current_blog_id() : 0;
        }

        $ids = get_sites(
            [
                'fields'  => 'ids',
                'number'  => 1,
                'offset'  => max( 0, $offset ),
                'orderby' => 'id',
                'order'   => 'ASC',
            ]
        );

        if ( ! is_array( $ids ) || empty( $ids ) ) {
            return 0;
        }

        return max( 0, (int) reset( $ids ) );
    }

    private function build_site_context( int $blog_id ): array {
        global $wpdb;

        $prefix = $wpdb->get_blog_prefix( $blog_id );

        return [
            'blog_id'   => $blog_id,
            'cap_key'   => $prefix . 'capabilities',
            'level_key' => $prefix . 'user_level',
            'roles'     => $this->load_role_definitions( $blog_id ),
        ];
    }

    private function load_role_definitions( int $blog_id ): array {
        global $wpdb;

        $switched = false;
        if ( is_multisite() && function_exists( 'switch_to_blog' ) && get_current_blog_id() !== $blog_id ) {
            switch_to_blog( $blog_id );
            $switched = true;
        }

        try {
            $option_name = $wpdb->prefix . 'user_roles';
            $roles = get_option( $option_name, [] );
        } finally {
            if ( $switched && function_exists( 'restore_current_blog' ) ) {
                restore_current_blog();
            }
        }

        if ( ! is_array( $roles ) ) {
            return [];
        }

        $normalized = [];
        foreach ( $roles as $role_key => $definition ) {
            $role = sanitize_key( (string) $role_key );
            if ( $role === '' ) {
                continue;
            }

            $caps = [];
            if ( is_array( $definition ) && isset( $definition['capabilities'] ) && is_array( $definition['capabilities'] ) ) {
                $caps = $definition['capabilities'];
            } elseif ( is_object( $definition ) && isset( $definition->capabilities ) && is_array( $definition->capabilities ) ) {
                $caps = $definition->capabilities;
            }

            $normalized[ $role ] = $this->truthy_keys( $caps );
        }

        return $normalized;
    }

    private function fetch_capability_rows( string $cap_key, int $last_umeta_id, int $limit ): array {
        global $wpdb;

        $table = self::table_identifier( $wpdb->usermeta );
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Core usermeta table identifier is validated by table_identifier().
        $sql = $wpdb->prepare(
            "SELECT umeta_id, user_id, meta_value FROM {$table} WHERE meta_key = %s AND umeta_id > %d ORDER BY umeta_id ASC LIMIT %d",
            $cap_key,
            max( 0, $last_umeta_id ),
            max( 1, $limit )
        );
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter

        if ( ! is_string( $sql ) || $sql === '' ) {
            return [];
        }

        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Capability rows are inspected directly because hidden privilege can be missed by higher-level user APIs.
        $rows = $wpdb->get_results( $sql, ARRAY_A );
        return is_array( $rows ) ? $rows : [];
    }

    private function fetch_capability_rows_desc( string $cap_key, int $before_umeta_id, int $limit ): array {
        global $wpdb;

        $table = self::table_identifier( $wpdb->usermeta );
        $safe_limit = max( 1, $limit );
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Core usermeta table identifier is validated by table_identifier().
        if ( $before_umeta_id > 0 ) {
            $sql = $wpdb->prepare(
                "SELECT umeta_id, user_id, meta_value
                 FROM {$table}
                 WHERE meta_key = %s AND umeta_id < %d
                 ORDER BY umeta_id DESC
                 LIMIT %d",
                $cap_key,
                $before_umeta_id,
                $safe_limit
            );
        } else {
            $sql = $wpdb->prepare(
                "SELECT umeta_id, user_id, meta_value
                 FROM {$table}
                 WHERE meta_key = %s
                 ORDER BY umeta_id DESC
                 LIMIT %d",
                $cap_key,
                $safe_limit
            );
        }
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter

        if ( ! is_string( $sql ) || $sql === '' ) {
            return [];
        }

        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Privileged capability rows are scanned newest-first so recent concealed accounts are verified first.
        $rows = $wpdb->get_results( $sql, ARRAY_A );
        return is_array( $rows ) ? $rows : [];
    }

    private function normalize_hidden_batch( $batch ): array {
        if ( ! is_array( $batch ) ) {
            return [];
        }

        $candidates = [];
        $raw_candidates = isset( $batch['candidates'] ) && is_array( $batch['candidates'] ) ? $batch['candidates'] : [];
        foreach ( $raw_candidates as $candidate ) {
            if ( ! is_array( $candidate ) ) {
                continue;
            }

            $user_id = max( 0, (int) ( $candidate['user_id'] ?? 0 ) );
            if ( $user_id <= 0 ) {
                continue;
            }

            $candidates[] = [
                'user_id'           => $user_id,
                'umeta_id'          => max( 0, (int) ( $candidate['umeta_id'] ?? 0 ) ),
                'has_admin_role'    => ! empty( $candidate['has_admin_role'] ),
                'roles'             => $this->sanitize_string_list( $candidate['roles'] ?? [] ),
                'direct_caps'       => $this->sanitize_string_list( $candidate['direct_caps'] ?? [] ),
                'privilege_sources' => $this->sanitize_string_list( $candidate['privilege_sources'] ?? [] ),
                'found_in_all'      => ! empty( $candidate['found_in_all'] ),
                'found_in_admin'    => ! empty( $candidate['found_in_admin'] ),
            ];
        }

        if ( empty( $candidates ) ) {
            return [];
        }

        return [
            'next_before_umeta_id' => max( 0, (int) ( $batch['next_before_umeta_id'] ?? 0 ) ),
            'min_user_id'          => max( 0, (int) ( $batch['min_user_id'] ?? 0 ) ),
            'all_offset'           => max( 0, (int) ( $batch['all_offset'] ?? 0 ) ),
            'all_floor_id'         => max( 0, (int) ( $batch['all_floor_id'] ?? 0 ) ),
            'all_exhausted'        => ! empty( $batch['all_exhausted'] ),
            'admin_offset'         => max( 0, (int) ( $batch['admin_offset'] ?? 0 ) ),
            'admin_floor_id'       => max( 0, (int) ( $batch['admin_floor_id'] ?? 0 ) ),
            'admin_exhausted'      => ! empty( $batch['admin_exhausted'] ),
            'has_admin_candidates' => ! empty( $batch['has_admin_candidates'] ),
            'source_exhausted'     => ! empty( $batch['source_exhausted'] ),
            'candidates'           => $candidates,
        ];
    }

    private function inspect_hidden_privileged_accounts( array $context, array $state, int $start_time, int $time_limit_seconds ): array {
        $issues = [];
        $visibility_pages_loaded = 0;

        while ( ! $this->time_exceeded( $start_time, $time_limit_seconds ) ) {
            $batch = $this->normalize_hidden_batch( $state['hidden_batch'] ?? [] );
            if ( empty( $batch ) ) {
                $batch = $this->build_hidden_candidate_batch(
                    $context,
                    max( 0, (int) ( $state['hidden_before_umeta_id'] ?? 0 ) )
                );

                if ( empty( $batch['candidates'] ) ) {
                    if ( ! empty( $batch['source_exhausted'] ) ) {
                        $state['hidden_checked'] = true;
                        $state['hidden_before_umeta_id'] = 0;
                        $state['hidden_batch'] = [];
                        return [ 'issues' => $issues, 'complete' => true, 'state' => $state ];
                    }

                    $state['hidden_before_umeta_id'] = max( 0, (int) ( $batch['next_before_umeta_id'] ?? 0 ) );
                    $state['hidden_batch'] = [];
                    continue;
                }
            }

            while ( ! $this->hidden_batch_has_visibility_coverage( $batch ) ) {
                if (
                    $this->time_exceeded( $start_time, $time_limit_seconds )
                    || $visibility_pages_loaded >= self::HIDDEN_MAX_VISIBLE_PAGES_PER_SLICE
                ) {
                    $state['hidden_batch'] = $batch;
                    $state['hidden_checked'] = false;
                    return [ 'issues' => $issues, 'complete' => false, 'state' => $state ];
                }

                if ( ! $this->hidden_batch_has_all_user_coverage( $batch ) ) {
                    $batch = $this->load_hidden_visibility_page( $batch, (int) $context['blog_id'], 'all' );
                    $visibility_pages_loaded++;
                    continue;
                }

                if ( ! $this->hidden_batch_has_admin_coverage( $batch ) ) {
                    $batch = $this->load_hidden_visibility_page( $batch, (int) $context['blog_id'], 'administrator' );
                    $visibility_pages_loaded++;
                    continue;
                }
            }

            $issues = array_merge( $issues, $this->finalize_hidden_candidate_batch( $context, $batch ) );

            $state['hidden_before_umeta_id'] = max( 0, (int) ( $batch['next_before_umeta_id'] ?? 0 ) );
            $state['hidden_batch'] = [];

            if ( ! empty( $batch['source_exhausted'] ) ) {
                $state['hidden_checked'] = true;
                return [ 'issues' => $issues, 'complete' => true, 'state' => $state ];
            }
        }

        $state['hidden_checked'] = false;
        $state['hidden_batch'] = $this->normalize_hidden_batch( $state['hidden_batch'] ?? [] );
        return [ 'issues' => $issues, 'complete' => false, 'state' => $state ];
    }

    private function build_hidden_candidate_batch( array $context, int $before_umeta_id ): array {
        $rows = $this->fetch_capability_rows_desc(
            (string) $context['cap_key'],
            $before_umeta_id,
            self::HIDDEN_CANDIDATE_BATCH_SIZE
        );

        if ( empty( $rows ) ) {
            return [
                'source_exhausted'     => true,
                'next_before_umeta_id' => 0,
                'candidates'           => [],
            ];
        }

        $candidates = [];
        $min_user_id = 0;
        $next_before_umeta_id = 0;
        $has_admin_candidates = false;

        foreach ( $rows as $row ) {
            $umeta_id = max( 0, (int) ( $row['umeta_id'] ?? 0 ) );
            if ( $next_before_umeta_id === 0 || ( $umeta_id > 0 && $umeta_id < $next_before_umeta_id ) ) {
                $next_before_umeta_id = $umeta_id;
            }

            $candidate = $this->build_hidden_candidate_from_row( $context, $row );
            if ( empty( $candidate ) ) {
                continue;
            }

            $user_id = (int) $candidate['user_id'];
            if ( isset( $candidates[ $user_id ] ) ) {
                continue;
            }

            $candidates[ $user_id ] = $candidate;
            $min_user_id = $min_user_id > 0 ? min( $min_user_id, $user_id ) : $user_id;
            $has_admin_candidates = $has_admin_candidates || ! empty( $candidate['has_admin_role'] );
        }

        return [
            'next_before_umeta_id' => max( 0, $next_before_umeta_id ),
            'min_user_id'          => max( 0, $min_user_id ),
            'all_offset'           => 0,
            'all_floor_id'         => 0,
            'all_exhausted'        => false,
            'admin_offset'         => 0,
            'admin_floor_id'       => 0,
            'admin_exhausted'      => false,
            'has_admin_candidates' => $has_admin_candidates,
            'source_exhausted'     => count( $rows ) < self::HIDDEN_CANDIDATE_BATCH_SIZE,
            'candidates'           => array_values( $candidates ),
        ];
    }

    private function build_hidden_candidate_from_row( array $context, array $row ): array {
        $user_id = max( 0, (int) ( $row['user_id'] ?? 0 ) );
        if ( $user_id <= 0 ) {
            return [];
        }

        $raw = (string) ( $row['meta_value'] ?? '' );
        $parsed = $this->parse_caps( $raw );
        if ( ! $parsed['valid'] ) {
            return [];
        }

        $classification = $this->classify_caps( $parsed['caps'], $context['roles'] );
        if ( ! $classification['is_privileged'] ) {
            return [];
        }

        return [
            'user_id'           => $user_id,
            'umeta_id'          => max( 0, (int) ( $row['umeta_id'] ?? 0 ) ),
            'has_admin_role'    => ! empty( $classification['has_admin_role'] ),
            'roles'             => $classification['roles'],
            'direct_caps'       => $classification['direct_caps'],
            'privilege_sources' => $classification['privilege_sources'],
            'found_in_all'      => false,
            'found_in_admin'    => false,
        ];
    }

    private function hidden_batch_has_visibility_coverage( array $batch ): bool {
        return $this->hidden_batch_has_all_user_coverage( $batch ) && $this->hidden_batch_has_admin_coverage( $batch );
    }

    private function hidden_batch_has_all_user_coverage( array $batch ): bool {
        if ( ! empty( $batch['all_exhausted'] ) ) {
            return true;
        }

        $min_user_id = max( 0, (int) ( $batch['min_user_id'] ?? 0 ) );
        $floor_id = max( 0, (int) ( $batch['all_floor_id'] ?? 0 ) );
        return $min_user_id > 0 && $floor_id > 0 && $floor_id <= $min_user_id;
    }

    private function hidden_batch_has_admin_coverage( array $batch ): bool {
        if ( empty( $batch['has_admin_candidates'] ) ) {
            return true;
        }

        if ( ! empty( $batch['admin_exhausted'] ) ) {
            return true;
        }

        $min_user_id = max( 0, (int) ( $batch['min_user_id'] ?? 0 ) );
        $floor_id = max( 0, (int) ( $batch['admin_floor_id'] ?? 0 ) );
        return $min_user_id > 0 && $floor_id > 0 && $floor_id <= $min_user_id;
    }

    private function load_hidden_visibility_page( array $batch, int $blog_id, string $scope ): array {
        $is_admin_scope = ( $scope === 'administrator' );
        $offset_key = $is_admin_scope ? 'admin_offset' : 'all_offset';
        $floor_key = $is_admin_scope ? 'admin_floor_id' : 'all_floor_id';
        $exhausted_key = $is_admin_scope ? 'admin_exhausted' : 'all_exhausted';

        $page = $this->fetch_visible_user_ids_page(
            $blog_id,
            $is_admin_scope ? 'administrator' : 'all',
            max( 0, (int) ( $batch[ $offset_key ] ?? 0 ) ),
            self::HIDDEN_VISIBLE_PAGE_SIZE
        );

        $batch = $this->mark_hidden_batch_visibility( $batch, $page['ids'], $is_admin_scope ? 'admin' : 'all' );
        $batch[ $offset_key ] = $page['next_offset'];
        $batch[ $floor_key ] = $page['floor_id'];
        $batch[ $exhausted_key ] = $page['exhausted'];

        return $batch;
    }

    private function fetch_visible_user_ids_page( int $blog_id, string $scope, int $offset, int $limit ): array {
        if ( ! function_exists( 'get_users' ) ) {
            return [
                'ids'         => [],
                'next_offset' => max( 0, $offset ),
                'floor_id'    => 0,
                'exhausted'   => true,
            ];
        }

        $args = [
            'blog_id'     => max( 1, $blog_id ),
            'fields'      => 'ID',
            'number'      => max( 1, $limit ),
            'offset'      => max( 0, $offset ),
            'orderby'     => 'ID',
            'order'       => 'DESC',
            'count_total' => false,
        ];
        if ( $scope === 'administrator' ) {
            $args['role'] = 'administrator';
        }

        $results = get_users( $args );
        $ids = [];
        if ( is_array( $results ) ) {
            foreach ( $results as $result ) {
                $id = is_object( $result )
                    ? ( isset( $result->ID ) ? (int) $result->ID : 0 )
                    : (int) $result;
                if ( $id > 0 ) {
                    $ids[] = $id;
                }
            }
        }

        $ids = array_values( array_unique( $ids ) );

        return [
            'ids'         => $ids,
            'next_offset' => max( 0, $offset ) + count( $ids ),
            'floor_id'    => empty( $ids ) ? 0 : min( $ids ),
            'exhausted'   => count( $ids ) < max( 1, $limit ),
        ];
    }

    private function mark_hidden_batch_visibility( array $batch, array $ids, string $scope ): array {
        if ( empty( $ids ) || empty( $batch['candidates'] ) || ! is_array( $batch['candidates'] ) ) {
            return $batch;
        }

        $visible = array_fill_keys( array_map( 'intval', $ids ), true );
        $flag_key = $scope === 'admin' ? 'found_in_admin' : 'found_in_all';

        foreach ( $batch['candidates'] as $index => $candidate ) {
            $user_id = max( 0, (int) ( $candidate['user_id'] ?? 0 ) );
            if ( $user_id > 0 && isset( $visible[ $user_id ] ) ) {
                $batch['candidates'][ $index ][ $flag_key ] = true;
            }
        }

        return $batch;
    }

    private function finalize_hidden_candidate_batch( array $context, array $batch ): array {
        $issues = [];

        foreach ( $batch['candidates'] as $candidate ) {
            if ( ! is_array( $candidate ) ) {
                continue;
            }

            $user_id = max( 0, (int) ( $candidate['user_id'] ?? 0 ) );
            if ( $user_id <= 0 ) {
                continue;
            }

            $hidden_views = [];
            if ( empty( $candidate['found_in_all'] ) ) {
                $hidden_views[] = 'all_users';
            }
            if ( ! empty( $candidate['has_admin_role'] ) && empty( $candidate['found_in_admin'] ) ) {
                $hidden_views[] = 'administrator_view';
            }
            if ( empty( $hidden_views ) ) {
                continue;
            }

            $user = get_userdata( $user_id );
            if ( ! ( $user instanceof \WP_User ) ) {
                continue;
            }

            $login = (string) $user->user_login;
            $visibility = $this->resolve_visibility( $user_id, (int) $context['blog_id'] );
            $evidence = [
                'wp_roles'                    => $visibility['roles'],
                'wp_can_manage_options'       => $visibility['can_manage_options'],
                'visible_in_all_users_query'  => ! empty( $candidate['found_in_all'] ),
                'visible_in_admin_user_query' => ! empty( $candidate['has_admin_role'] ) ? ! empty( $candidate['found_in_admin'] ) : null,
                'hidden_views'                => $hidden_views,
                'user_level'                  => $this->get_user_level( $user_id, (string) $context['level_key'] ),
                'missing_profile_meta'        => $this->get_missing_profile_meta( $user_id ),
            ];

            $issues[] = $this->build_issue(
                'hidden_privileged_user',
                'high',
                'hidden_privileged_user_' . (int) $context['blog_id'] . '_' . $user_id,
                "Privileged account '{$login}' is missing from standard WordPress user listings.",
                'User: ' . $login,
                [
                    'user_id'           => $user_id,
                    'user_login'        => $login,
                    'user_email'        => (string) $user->user_email,
                    'blog_id'           => (int) $context['blog_id'],
                    'cap_key'           => (string) $context['cap_key'],
                    'umeta_id'          => max( 0, (int) ( $candidate['umeta_id'] ?? 0 ) ),
                    'privilege_sources' => $this->sanitize_string_list( $candidate['privilege_sources'] ?? [] ),
                    'roles'             => $this->sanitize_string_list( $candidate['roles'] ?? [] ),
                    'direct_caps'       => $this->sanitize_string_list( $candidate['direct_caps'] ?? [] ),
                    'hidden_views'      => $hidden_views,
                    'evidence'          => $evidence,
                ]
            );
        }

        return $issues;
    }

    private function inspect_capability_row( array $context, array $row ): array {
        $umeta_id = max( 0, (int) ( $row['umeta_id'] ?? 0 ) );
        $user_id = max( 0, (int) ( $row['user_id'] ?? 0 ) );
        $raw = (string) ( $row['meta_value'] ?? '' );

        if ( $user_id <= 0 ) {
            return [];
        }

        $parsed = $this->parse_caps( $raw );
        if ( ! $parsed['valid'] ) {
            if ( ! $this->raw_has_privileged_marker( $raw ) ) {
                return [];
            }

            $user = get_userdata( $user_id );
            $login = $user instanceof \WP_User ? (string) $user->user_login : 'user#' . $user_id;

            return [
                $this->build_issue(
                    'malformed_privileged_capability_meta',
                    'high',
                    'malformed_privileged_capability_meta_' . (int) $context['blog_id'] . '_' . $umeta_id,
                    'Malformed capability metadata contains privileged markers.',
                    'System Users',
                    [
                        'user_id'    => $user_id,
                        'user_login' => $login,
                        'blog_id'    => (int) $context['blog_id'],
                        'cap_key'    => (string) $context['cap_key'],
                        'umeta_id'   => $umeta_id,
                        'raw_length' => strlen( $raw ),
                        'raw_sha256' => hash( 'sha256', $raw ),
                    ]
                ),
            ];
        }

        $classification = $this->classify_caps( $parsed['caps'], $context['roles'] );
        if ( ! $classification['is_privileged'] ) {
            return [];
        }

        $user = get_userdata( $user_id );
        if ( ! ( $user instanceof \WP_User ) ) {
            return [];
        }

        $issues = [];
        $login = (string) $user->user_login;
        $visibility = $this->resolve_visibility( $user_id, (int) $context['blog_id'] );
        $evidence = [
            'wp_roles'              => $visibility['roles'],
            'wp_can_manage_options' => $visibility['can_manage_options'],
            'user_level'            => $this->get_user_level( $user_id, (string) $context['level_key'] ),
            'missing_profile_meta' => $this->get_missing_profile_meta( $user_id ),
        ];

        if ( $classification['expected_user_level'] >= 0 && (int) $evidence['user_level'] !== (int) $classification['expected_user_level'] ) {
            $issues[] = $this->build_issue(
                'inconsistent_privileged_user_level',
                (int) $classification['expected_user_level'] >= 10 ? 'high' : 'medium',
                'inconsistent_privileged_user_level_' . (int) $context['blog_id'] . '_' . $user_id,
                "Privileged account '{$login}' has inconsistent WordPress user-level metadata.",
                'User: ' . $login,
                [
                    'user_id'             => $user_id,
                    'user_login'          => $login,
                    'user_email'          => (string) $user->user_email,
                    'blog_id'             => (int) $context['blog_id'],
                    'cap_key'             => (string) $context['cap_key'],
                    'level_key'           => (string) $context['level_key'],
                    'umeta_id'            => $umeta_id,
                    'expected_user_level' => (int) $classification['expected_user_level'],
                    'actual_user_level'   => (int) $evidence['user_level'],
                    'privilege_sources'   => $classification['privilege_sources'],
                    'roles'               => $classification['roles'],
                    'direct_caps'         => $classification['direct_caps'],
                    'evidence'            => $evidence,
                ]
            );
        }

        if ( $classification['is_shadow_manage_user'] ) {
            $shadow_severity = $this->resolve_shadow_manage_severity( $classification );
            $issues[] = $this->build_issue(
                'shadow_manage_capability',
                $shadow_severity,
                'shadow_manage_capability_' . (int) $context['blog_id'] . '_' . $user_id,
                "User '{$login}' has administrative management capability outside the administrator role.",
                'User: ' . $login,
                [
                    'user_id'           => $user_id,
                    'user_login'        => $login,
                    'user_email'        => (string) $user->user_email,
                    'blog_id'           => (int) $context['blog_id'],
                    'cap_key'           => (string) $context['cap_key'],
                    'umeta_id'          => $umeta_id,
                    'privilege_sources' => $classification['privilege_sources'],
                    'roles'             => $classification['roles'],
                    'direct_caps'       => $classification['direct_caps'],
                    'evidence'          => $evidence,
                ]
            );
        }

        $event = $this->find_suspicious_event( $user_id, (int) $context['blog_id'] );
        if ( ! empty( $event ) ) {
            $issues[] = $this->build_issue(
                'privileged_creation_event_anomaly',
                'critical',
                'privileged_creation_event_anomaly_' . (int) $context['blog_id'] . '_' . $user_id,
                "Suspicious privileged account creation or elevation observed for '{$login}'.",
                'User: ' . $login,
                [
                    'user_id'           => $user_id,
                    'user_login'        => $login,
                    'user_email'        => (string) $user->user_email,
                    'blog_id'           => (int) $context['blog_id'],
                    'privilege_sources' => $classification['privilege_sources'],
                    'roles'             => $classification['roles'],
                    'direct_caps'       => $classification['direct_caps'],
                    'event'             => $event,
                    'evidence'          => $evidence,
                ]
            );
        }

        return $issues;
    }

    private function parse_caps( string $raw ): array {
        $value = maybe_unserialize( $raw );
        if ( ! is_array( $value ) ) {
            return [ 'valid' => false, 'caps' => [] ];
        }

        return [ 'valid' => true, 'caps' => $value ];
    }

    private function classify_caps( array $caps, array $role_definitions ): array {
        $truthy = $this->truthy_keys( $caps );
        $roles = [];
        $direct_caps = [];
        $expanded_caps = [];

        foreach ( $truthy as $cap ) {
            if ( $cap === 'administrator' || isset( $role_definitions[ $cap ] ) ) {
                $roles[] = $cap;
                foreach ( $role_definitions[ $cap ] ?? [] as $role_cap ) {
                    $expanded_caps[ $role_cap ] = true;
                }
            } else {
                $direct_caps[] = $cap;
                $expanded_caps[ $cap ] = true;
            }
        }

        $roles = array_values( array_unique( $roles ) );
        $direct_caps = array_values( array_unique( $direct_caps ) );
        $has_admin_role = in_array( 'administrator', $roles, true );
        $has_manage_options = ! empty( $expanded_caps[ self::MANAGEMENT_CAP ] );
        $privilege_sources = [];

        if ( $has_admin_role ) {
            $privilege_sources[] = 'role:administrator';
        }

        if ( in_array( self::MANAGEMENT_CAP, $direct_caps, true ) ) {
            $privilege_sources[] = 'direct_cap:' . self::MANAGEMENT_CAP;
        }

        foreach ( $roles as $role ) {
            if ( $role === 'administrator' ) {
                continue;
            }
            if ( isset( $role_definitions[ $role ] ) && in_array( self::MANAGEMENT_CAP, $role_definitions[ $role ], true ) ) {
                $privilege_sources[] = 'role:' . $role . ':' . self::MANAGEMENT_CAP;
            }
        }

        $privilege_sources = array_values( array_unique( $privilege_sources ) );
        $has_shadow_role = ! $has_admin_role && $has_manage_options;
        $has_shadow_direct = ! $has_admin_role && in_array( self::MANAGEMENT_CAP, $direct_caps, true );

        return [
            'is_privileged'         => ! empty( $privilege_sources ),
            'has_admin_role'        => $has_admin_role,
            'has_manage_options'    => $has_manage_options,
            'is_shadow_manage_user' => $has_shadow_role || $has_shadow_direct,
            'expected_user_level'   => $this->expected_user_level( $expanded_caps, $has_admin_role ),
            'roles'                 => $roles,
            'direct_caps'           => $direct_caps,
            'privilege_sources'     => $privilege_sources,
        ];
    }

    private function expected_user_level( array $expanded_caps, bool $has_admin_role ): int {
        if ( $has_admin_role ) {
            return 10;
        }

        $max_level = -1;
        foreach ( $expanded_caps as $cap => $enabled ) {
            if ( ! $enabled ) {
                continue;
            }
            if ( preg_match( '/^level_([0-9]|10)$/', (string) $cap, $matches ) ) {
                $max_level = max( $max_level, (int) $matches[1] );
            }
        }

        return $max_level;
    }

    private function resolve_shadow_manage_severity( array $classification ): string {
        $sources = is_array( $classification['privilege_sources'] ?? null ) ? $classification['privilege_sources'] : [];
        foreach ( $sources as $source ) {
            if ( $source === 'direct_cap:' . self::MANAGEMENT_CAP ) {
                return 'high';
            }
        }

        return 'medium';
    }

    private function truthy_keys( array $values ): array {
        $keys = [];
        foreach ( $values as $key => $enabled ) {
            if ( ! $enabled ) {
                continue;
            }
            $cap = sanitize_key( (string) $key );
            if ( $cap !== '' ) {
                $keys[] = $cap;
            }
        }

        return array_values( array_unique( $keys ) );
    }

    private function resolve_visibility( int $user_id, int $blog_id ): array {
        $roles = [];
        $can_manage_options = false;

        $user = new \WP_User( $user_id );
        if ( method_exists( $user, 'for_site' ) ) {
            $user->for_site( $blog_id );
            $roles = is_array( $user->roles ) ? array_values( $user->roles ) : [];
        } else {
            $roles = is_array( $user->roles ) ? array_values( $user->roles ) : [];
        }

        if ( function_exists( 'user_can_for_site' ) ) {
            $can_manage_options = user_can_for_site( $user_id, $blog_id, 'manage_options' );
        } else {
            $switched = false;
            if ( is_multisite() && function_exists( 'switch_to_blog' ) && get_current_blog_id() !== $blog_id ) {
                switch_to_blog( $blog_id );
                $switched = true;
            }
            try {
                $can_manage_options = user_can( $user, 'manage_options' );
            } finally {
                if ( $switched && function_exists( 'restore_current_blog' ) ) {
                    restore_current_blog();
                }
            }
        }

        return [
            'roles'              => $roles,
            'has_admin_role'     => in_array( 'administrator', $roles, true ),
            'can_manage_options' => (bool) $can_manage_options,
        ];
    }

    private function get_user_level( int $user_id, string $level_key ): int {
        $value = get_user_meta( $user_id, $level_key, true );
        return is_numeric( $value ) ? (int) $value : -1;
    }

    private function get_missing_profile_meta( int $user_id ): array {
        $missing = [];
        foreach ( [ 'nickname', 'rich_editing', 'admin_color', 'show_admin_bar_front' ] as $key ) {
            $value = get_user_meta( $user_id, $key, true );
            if ( $value === '' || $value === null ) {
                $missing[] = $key;
            }
        }

        return $missing;
    }

    private function raw_has_privileged_marker( string $raw ): bool {
        $lower = strtolower( $raw );
        if ( strpos( $lower, 'administrator' ) !== false ) {
            return true;
        }

        return strpos( $lower, self::MANAGEMENT_CAP ) !== false;
    }

    private function inspect_network_super_admins( int $start_time, int $time_limit_seconds ): array {
        if ( ! is_multisite() || ! function_exists( 'get_super_admins' ) ) {
            return [];
        }

        $issues = [];
        $logins = get_super_admins();
        if ( ! is_array( $logins ) ) {
            return [];
        }

        foreach ( $logins as $login ) {
            $user = get_user_by( 'login', (string) $login );
            if ( ! ( $user instanceof \WP_User ) ) {
                continue;
            }

            if ( ! $this->is_network_super_admin_visible_in_user_listings( $user, $start_time, $time_limit_seconds ) ) {
                $issues[] = $this->build_issue(
                    'hidden_privileged_user',
                    'high',
                    'hidden_privileged_user_network_' . (int) $user->ID,
                    "Privileged account '{$user->user_login}' is missing from standard WordPress user listings.",
                    'User: ' . (string) $user->user_login,
                    [
                        'user_id'           => (int) $user->ID,
                        'user_login'        => (string) $user->user_login,
                        'user_email'        => (string) $user->user_email,
                        'blog_id'           => 0,
                        'privilege_sources' => [ 'network_super_admin' ],
                        'roles'             => [ 'super_admin' ],
                        'direct_caps'       => [],
                        'hidden_views'      => [ 'network_users' ],
                        'evidence'          => [
                            'visible_in_network_users_query' => false,
                            'hidden_views'                   => [ 'network_users' ],
                        ],
                    ]
                );
            }

            $event = $this->find_suspicious_event( (int) $user->ID, 0 );
            if ( empty( $event ) ) {
                continue;
            }

            $issues[] = $this->build_issue(
                'privileged_creation_event_anomaly',
                'critical',
                'privileged_creation_event_anomaly_network_' . (int) $user->ID,
                "Suspicious network administrator elevation observed for '{$user->user_login}'.",
                'User: ' . (string) $user->user_login,
                [
                    'user_id'           => (int) $user->ID,
                    'user_login'        => (string) $user->user_login,
                    'user_email'        => (string) $user->user_email,
                    'blog_id'           => 0,
                    'privilege_sources' => [ 'network_super_admin' ],
                    'roles'             => [ 'super_admin' ],
                    'direct_caps'       => [],
                    'event'             => $event,
                ]
            );
        }

        return $issues;
    }

    private function is_network_super_admin_visible_in_user_listings( \WP_User $user, int $start_time, int $time_limit_seconds ): bool {
        if ( ! function_exists( 'get_users' ) ) {
            return true;
        }

        if ( $this->time_exceeded( $start_time, $time_limit_seconds ) ) {
            return true;
        }

        $login = trim( (string) $user->user_login );
        if ( $login === '' ) {
            return true;
        }

        $results = get_users(
            [
                'fields'         => 'ID',
                'number'         => 20,
                'search'         => $login,
                'search_columns' => [ 'user_login' ],
                'orderby'        => 'ID',
                'order'          => 'DESC',
                'count_total'    => false,
            ]
        );

        if ( ! is_array( $results ) ) {
            return true;
        }

        $target_user_id = (int) $user->ID;
        foreach ( $results as $result ) {
            $visible_user_id = is_object( $result )
                ? ( isset( $result->ID ) ? (int) $result->ID : 0 )
                : (int) $result;
            if ( $visible_user_id === $target_user_id ) {
                return true;
            }
        }

        return false;
    }

    private function load_event_index(): array {
        $events = is_multisite()
            ? get_site_option( self::EVENT_OPTION, [] )
            : get_option( self::EVENT_OPTION, [] );

        if ( ! is_array( $events ) ) {
            return [];
        }

        $index = [];
        foreach ( array_slice( $events, 0, self::EVENT_SCAN_LIMIT ) as $event ) {
            if ( ! is_array( $event ) ) {
                continue;
            }

            $target_user_id = max( 0, (int) ( $event['target_user_id'] ?? 0 ) );
            if ( $target_user_id <= 0 || ! $this->is_event_suspicious( $event ) ) {
                continue;
            }

            $blog_id = max( 0, (int) ( $event['blog_id'] ?? 0 ) );
            $index[ $blog_id . ':' . $target_user_id ][] = $this->public_event_data( $event );
        }

        return $index;
    }

    private function find_suspicious_event( int $user_id, int $blog_id ): array {
        $specific = $this->events_by_target[ max( 0, $blog_id ) . ':' . $user_id ][0] ?? [];
        if ( is_array( $specific ) && ! empty( $specific ) ) {
            return $specific;
        }

        return [];
    }

    private function is_event_suspicious( array $event ): bool {
        $actor_id = max( 0, (int) ( $event['actor_user_id'] ?? 0 ) );
        if ( $actor_id > 0 ) {
            return false;
        }

        $channel = sanitize_key( (string) ( $event['channel'] ?? '' ) );
        if ( in_array( $channel, [ 'frontend', 'rest', 'ajax' ], true ) ) {
            return true;
        }

        $query_keys = $event['query_keys'] ?? [];
        return is_array( $query_keys ) && ! empty( $query_keys ) && ! in_array( $channel, [ 'cli', 'cron' ], true );
    }

    private function public_event_data( array $event ): array {
        return [
            'event_id'       => sanitize_text_field( (string) ( $event['id'] ?? '' ) ),
            'event_type'     => sanitize_key( (string) ( $event['event_type'] ?? '' ) ),
            'captured_at'    => max( 0, (int) ( $event['captured_at'] ?? 0 ) ),
            'actor_user_id'  => max( 0, (int) ( $event['actor_user_id'] ?? 0 ) ),
            'channel'        => sanitize_key( (string) ( $event['channel'] ?? '' ) ),
            'method'         => sanitize_key( (string) ( $event['method'] ?? '' ) ),
            'request_path'   => sanitize_text_field( (string) ( $event['request_path'] ?? '' ) ),
            'query_keys'     => $this->sanitize_string_list( $event['query_keys'] ?? [] ),
            'context_flags'  => $this->sanitize_string_list( $event['context_flags'] ?? [] ),
        ];
    }

    private function sanitize_string_list( $values ): array {
        if ( ! is_array( $values ) ) {
            return [];
        }

        $out = [];
        foreach ( $values as $value ) {
            $item = sanitize_key( (string) $value );
            if ( $item !== '' ) {
                $out[] = $item;
            }
        }

        return array_values( array_unique( $out ) );
    }

    private function build_issue( string $reason, string $severity, string $stable_key, string $message, string $file, array $data ): array {
        $data['reason'] = $reason;
        $hash = IssueRepository::generateHash( 'user', $stable_key );
        $user_id = isset( $data['user_id'] ) ? (int) $data['user_id'] : 0;
        if ( $user_id > 0 ) {
            $blog_id = isset( $data['blog_id'] ) ? (int) $data['blog_id'] : 0;
            $hash = IssueRepository::generateHash(
                'user',
                'user_account_risk_' . max( 0, $blog_id ) . '_' . $user_id
            );
        }

        return [
            'id'       => $stable_key,
            'hash'     => $hash,
            'type'     => 'user',
            'status'   => $severity === 'critical' ? 'critical' : 'warning',
            'severity' => $severity,
            'message'  => $message,
            'file'     => $file,
            'data'     => $data,
        ];
    }

    private static function table_identifier( string $table_name ): string {
        if ( $table_name === '' || strlen( $table_name ) > 64 || ! preg_match( '/^[A-Za-z0-9_]+$/', $table_name ) ) {
            throw new \InvalidArgumentException( 'Invalid table identifier.' );
        }

        return '`' . $table_name . '`';
    }
}
