<?php

declare(strict_types=1);

namespace ShieldX\Privacy;

defined('ABSPATH') || exit;

use ShieldX\Security\LoginSecurity;
use ShieldX\Security\UserProfiler;

final class PrivacyManager
{
    private const PAGE_SIZE = 100;

    /** @var array<int,string> */
    private const USER_META_KEYS = [
        'shieldx_overview_disabled_cards',
        'shieldx_overview_disabled_cards_initialized',
        'shieldx_admin_review_flag',
    ];

    /** @var object|null */
    private $wpdb;

    /** @var UserProfiler */
    private $userProfiler;

    public function __construct($wpdb = null, ?UserProfiler $userProfiler = null)
    {
        $this->wpdb = $wpdb;
        $this->userProfiler = $userProfiler ?: new UserProfiler($wpdb);
    }

    public static function fromWordPress(): self
    {
        global $wpdb;

        return new self($wpdb, UserProfiler::fromWordPress());
    }

    /**
     * @param array<string,array<string,mixed>> $exporters
     * @return array<string,array<string,mixed>>
     */
    public function registerExporters(array $exporters): array
    {
        $exporters['shieldx-security-data'] = [
            'exporter_friendly_name' => __('SHIELD-X security data', 'xerotact-shield-x-security'),
            'callback' => [$this, 'exportPersonalData'],
        ];

        return $exporters;
    }

    /**
     * @param array<string,array<string,mixed>> $erasers
     * @return array<string,array<string,mixed>>
     */
    public function registerErasers(array $erasers): array
    {
        $erasers['shieldx-security-data'] = [
            'eraser_friendly_name' => __('SHIELD-X security data', 'xerotact-shield-x-security'),
            'callback' => [$this, 'erasePersonalData'],
        ];

        return $erasers;
    }

    /**
     * @return array{data:array<int,array<string,mixed>>,done:bool}
     */
    public function exportPersonalData(string $emailAddress, int $page = 1): array
    {
        $userId = $this->userIdForEmail($emailAddress);
        if ($userId <= 0) {
            return [
                'data' => [],
                'done' => true,
            ];
        }

        $page = max(1, $page);
        $profileData = $this->userProfiler->personalDataForUser($userId, $page, self::PAGE_SIZE);
        $threatData = $this->threatDataForUser($userId, $page, self::PAGE_SIZE);
        $data = [];

        foreach ($profileData['rows'] as $row) {
            $data[] = $this->profileExportItem($row);
        }

        foreach ($threatData['rows'] as $row) {
            $data[] = $this->threatExportItem($row);
        }

        if ($page === 1) {
            $settingsItem = $this->settingsExportItem($userId);
            if ($settingsItem !== null) {
                $data[] = $settingsItem;
            }
        }

        return [
            'data' => $data,
            'done' => !empty($profileData['done']) && !empty($threatData['done']),
        ];
    }

    /**
     * @return array{items_removed:bool,items_retained:bool,messages:array<int,string>,done:bool}
     */
    public function erasePersonalData(string $emailAddress, int $page = 1): array
    {
        $userId = $this->userIdForEmail($emailAddress);
        if ($userId <= 0 || $page > 1) {
            return [
                'items_removed' => false,
                'items_retained' => false,
                'messages' => [],
                'done' => true,
            ];
        }

        $removed = $this->userProfiler->erasePersonalDataForUser($userId);
        $removed += $this->anonymizeThreatResolutions($userId);

        foreach (array_values(array_unique(array_merge(self::USER_META_KEYS, LoginSecurity::userMetaKeys()))) as $metaKey) {
            if (function_exists('delete_user_meta') && delete_user_meta($userId, $metaKey)) {
                $removed++;
            }
        }

        return [
            'items_removed' => $removed > 0,
            'items_retained' => true,
            'messages' => [
                __('SHIELD-X security audit logs are not changed by this eraser and remain subject to the site\'s configured log-retention policy.', 'xerotact-shield-x-security'),
            ],
            'done' => true,
        ];
    }

    private function userIdForEmail(string $emailAddress): int
    {
        if (!function_exists('get_user_by')) {
            return 0;
        }

        $user = get_user_by('email', trim($emailAddress));

        return is_object($user) && isset($user->ID) ? max(0, (int) $user->ID) : 0;
    }

    /**
     * @return array{rows:array<int,array<string,mixed>>,done:bool}
     */
    private function threatDataForUser(int $userId, int $page, int $perPage): array
    {
        $table = $this->table('shieldx_threats');
        if ($table === '' || !$this->tableExists($table) || !method_exists($this->wpdb, 'get_results')) {
            return [
                'rows' => [],
                'done' => true,
            ];
        }

        $offset = (max(1, $page) - 1) * $perPage;
        $sql = $this->prepareTableSql(
            $table,
            'SELECT id, threat_id, type, severity, status, source, description, affected_path, resolved_at
             FROM %i
             WHERE resolved_by = %d
             ORDER BY resolved_at ASC, id ASC
             LIMIT %d OFFSET %d',
            [$userId, $perPage + 1, $offset]
        );
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- The helper validates the table identifier and prepares all values.
        $rows = $this->wpdb->get_results($sql, defined('ARRAY_A') ? ARRAY_A : 'ARRAY_A');
        $rows = is_array($rows) ? $rows : [];
        $done = count($rows) <= $perPage;
        if (!$done) {
            $rows = array_slice($rows, 0, $perPage);
        }

        return [
            'rows' => $rows,
            'done' => $done,
        ];
    }

    private function anonymizeThreatResolutions(int $userId): int
    {
        $table = $this->table('shieldx_threats');
        if ($table === '' || !$this->tableExists($table) || !method_exists($this->wpdb, 'update')) {
            return 0;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- WordPress privacy erasure removes only the user attribution from SHIELD-X threat records.
        $updated = $this->wpdb->update(
            $table,
            ['resolved_by' => null],
            ['resolved_by' => $userId],
            ['%d'],
            ['%d']
        );

        return $updated === false ? 0 : max(0, (int) $updated);
    }

    /**
     * @param array<string,mixed> $row
     * @return array<string,mixed>
     */
    private function profileExportItem(array $row): array
    {
        return [
            'group_id' => 'shieldx-security-activity',
            'group_label' => __('SHIELD-X security activity', 'xerotact-shield-x-security'),
            'item_id' => 'shieldx-security-activity-' . max(0, (int) ($row['id'] ?? 0)),
            'data' => [
                ['name' => __('Date', 'xerotact-shield-x-security'), 'value' => (string) ($row['visited_at'] ?? '')],
                ['name' => __('Request method', 'xerotact-shield-x-security'), 'value' => (string) ($row['method'] ?? '')],
                ['name' => __('Requested URL', 'xerotact-shield-x-security'), 'value' => (string) ($row['uri'] ?? '')],
                ['name' => __('Referrer', 'xerotact-shield-x-security'), 'value' => (string) ($row['referer'] ?? '')],
                ['name' => __('IP address', 'xerotact-shield-x-security'), 'value' => (string) ($row['ip'] ?? '')],
                ['name' => __('Country', 'xerotact-shield-x-security'), 'value' => (string) ($row['country_code'] ?? '')],
                ['name' => __('Browser', 'xerotact-shield-x-security'), 'value' => (string) ($row['browser'] ?? '')],
                ['name' => __('User agent', 'xerotact-shield-x-security'), 'value' => (string) ($row['user_agent'] ?? '')],
                ['name' => __('Admin request', 'xerotact-shield-x-security'), 'value' => !empty($row['is_admin']) ? __('Yes', 'xerotact-shield-x-security') : __('No', 'xerotact-shield-x-security')],
            ],
        ];
    }

    /**
     * @param array<string,mixed> $row
     * @return array<string,mixed>
     */
    private function threatExportItem(array $row): array
    {
        return [
            'group_id' => 'shieldx-threat-actions',
            'group_label' => __('SHIELD-X threat actions', 'xerotact-shield-x-security'),
            'item_id' => 'shieldx-threat-action-' . max(0, (int) ($row['id'] ?? 0)),
            'data' => [
                ['name' => __('Threat ID', 'xerotact-shield-x-security'), 'value' => (string) ($row['threat_id'] ?? '')],
                ['name' => __('Type', 'xerotact-shield-x-security'), 'value' => (string) ($row['type'] ?? '')],
                ['name' => __('Severity', 'xerotact-shield-x-security'), 'value' => (string) ($row['severity'] ?? '')],
                ['name' => __('Status', 'xerotact-shield-x-security'), 'value' => (string) ($row['status'] ?? '')],
                ['name' => __('Source', 'xerotact-shield-x-security'), 'value' => (string) ($row['source'] ?? '')],
                ['name' => __('Description', 'xerotact-shield-x-security'), 'value' => (string) ($row['description'] ?? '')],
                ['name' => __('Affected path', 'xerotact-shield-x-security'), 'value' => (string) ($row['affected_path'] ?? '')],
                ['name' => __('Resolved at', 'xerotact-shield-x-security'), 'value' => (string) ($row['resolved_at'] ?? '')],
            ],
        ];
    }

    /**
     * @return array<string,mixed>|null
     */
    private function settingsExportItem(int $userId): ?array
    {
        if (!function_exists('get_user_meta')) {
            return null;
        }

        $twoFactorEnabled = get_user_meta($userId, 'shieldx_2fa_enabled', true);
        $twoFactorEnabledAt = get_user_meta($userId, 'shieldx_2fa_enabled_at', true);
        $twoFactorRecoveryUsedAt = get_user_meta($userId, 'shieldx_2fa_recovery_used_at', true);
        $disabledCards = get_user_meta($userId, 'shieldx_overview_disabled_cards', true);
        $reviewFlag = get_user_meta($userId, 'shieldx_admin_review_flag', true);
        if ($twoFactorEnabled === '' && $twoFactorEnabledAt === '' && $twoFactorRecoveryUsedAt === '' && $disabledCards === '' && $reviewFlag === '') {
            return null;
        }

        return [
            'group_id' => 'shieldx-user-settings',
            'group_label' => __('SHIELD-X user settings', 'xerotact-shield-x-security'),
            'item_id' => 'shieldx-user-settings-' . $userId,
            'data' => [
                ['name' => __('Two-factor authentication enabled', 'xerotact-shield-x-security'), 'value' => !empty($twoFactorEnabled) ? __('Yes', 'xerotact-shield-x-security') : __('No', 'xerotact-shield-x-security')],
                ['name' => __('Two-factor authentication enabled at', 'xerotact-shield-x-security'), 'value' => (string) $twoFactorEnabledAt],
                ['name' => __('Recovery code last used at', 'xerotact-shield-x-security'), 'value' => (string) $twoFactorRecoveryUsedAt],
                ['name' => __('Hidden overview cards', 'xerotact-shield-x-security'), 'value' => $this->displayValue($disabledCards)],
                ['name' => __('Administrator review flag', 'xerotact-shield-x-security'), 'value' => $this->displayValue($reviewFlag)],
            ],
        ];
    }

    /**
     * @param mixed $value
     */
    private function displayValue($value): string
    {
        if (is_scalar($value) || $value === null) {
            return (string) $value;
        }

        $encoded = wp_json_encode($value);

        return is_string($encoded) ? $encoded : '';
    }

    private function table(string $suffix): string
    {
        if (!is_object($this->wpdb) || !isset($this->wpdb->prefix) || preg_match('/^shieldx_[a-z_]+$/', $suffix) !== 1) {
            return '';
        }

        return (string) $this->wpdb->prefix . $suffix;
    }

    private function tableExists(string $table): bool
    {
        if (!method_exists($this->wpdb, 'get_var') || !method_exists($this->wpdb, 'prepare')) {
            return false;
        }

        $prepared = $this->wpdb->prepare('SHOW TABLES LIKE %s', $table);
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared -- The table name is passed through the placeholder above.
        $found = $this->wpdb->get_var($prepared);

        return (string) $found === $table;
    }

    /**
     * @param array<int,mixed> $args
     */
    private function prepareTableSql(string $table, string $sql, array $args = []): string
    {
        if (!is_object($this->wpdb) || !method_exists($this->wpdb, 'prepare') || preg_match('/^[A-Za-z0-9_]+$/', $table) !== 1) {
            return '';
        }

        $sql = str_replace('%i', '`' . str_replace('`', '``', $table) . '`', $sql);
        if ($args === []) {
            return $sql;
        }

        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Callers provide fixed SQL templates; this method validates the table identifier and prepares every value.
        $prepared = $this->wpdb->prepare($sql, ...$args);

        return is_string($prepared) ? $prepared : '';
    }
}
