<?php
declare(strict_types=1);

namespace Help4BuilderSuite\Modules\Forms;

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

/** Preview-only evidence for legacy Form Entry timestamps. Never rewrites entries. */
final class TimestampReadiness
{
    private const OPTION = 'h4bs_form_timestamp_basis_evidence';
    private const MAX_SCAN = 10000;

    public function boot(): void
    {
        add_action('admin_post_h4bs_save_form_timestamp_evidence', [$this, 'save']);
    }

    /** @param array<string,mixed> $policy */
    public function renderPanel(array $policy): void
    {
        $readiness = $this->readiness($policy);
        $canManage = current_user_can('manage_help4_form_privacy');
        ?>
        <section class="h4bs-privacy-panel h4bs-timestamp-readiness" aria-labelledby="h4bs-timestamp-readiness-title">
            <h2 id="h4bs-timestamp-readiness-title"><?php esc_html_e('Older message date readiness', 'help4-builder-suite'); ?></h2>
            <p><?php esc_html_e('New messages are marked as UTC. Older records without that marker stay unresolved until an administrator records site-specific evidence. This preview changes no stored message.', 'help4-builder-suite'); ?></p>
            <div class="h4bs-timestamp-summary">
                <p><strong><?php echo esc_html((string) $readiness['known']); ?></strong> <?php esc_html_e('messages have an explicit UTC basis', 'help4-builder-suite'); ?></p>
                <p><strong><?php echo esc_html((string) $readiness['legacy']); ?></strong> <?php esc_html_e('older messages need a verified basis', 'help4-builder-suite'); ?></p>
            </div>
            <?php if (! $readiness['valid']) : ?>
                <div class="notice notice-error inline" role="alert"><p><?php echo esc_html((string) $readiness['error']); ?></p></div>
            <?php elseif ((int) $readiness['legacy'] === 0) : ?>
                <div class="notice notice-success inline"><p><strong><?php esc_html_e('Ready.', 'help4-builder-suite'); ?></strong> <?php esc_html_e('Every stored message has an explicit UTC timestamp basis.', 'help4-builder-suite'); ?></p></div>
            <?php elseif ((string) $readiness['basis'] === 'unresolved') : ?>
                <div class="notice notice-warning inline"><p><strong><?php esc_html_e('Older dates are unresolved.', 'help4-builder-suite'); ?></strong> <?php esc_html_e('Do not treat age-based results for those messages as authoritative. Exclude affected forms, place holds on exceptions, or export the exact set for authorized offline review.', 'help4-builder-suite'); ?></p></div>
            <?php else : ?>
                <div class="notice notice-info inline"><p><strong><?php esc_html_e('Preview only—no migration applied.', 'help4-builder-suite'); ?></strong> <?php echo esc_html((string) $readiness['basis_label']); ?> <?php echo esc_html((string) $readiness['delta_label']); ?></p></div>
                <?php if (! empty($readiness['sample'])) : ?><details><summary><?php esc_html_e('Review non-personal date examples', 'help4-builder-suite'); ?></summary><ol><?php foreach ($readiness['sample'] as $row) : ?><li><?php
                    /* translators: 1: message ID, 2: form ID, 3: stored date, 4: proposed UTC date. */
                    printf(esc_html__('Message #%1$d · Form #%2$d · stored %3$s · preview UTC %4$s', 'help4-builder-suite'), (int) $row['id'], (int) $row['form_id'], esc_html((string) $row['stored']), esc_html((string) $row['utc']));
                ?></li><?php endforeach; ?></ol></details><?php endif; ?>
            <?php endif; ?>
            <?php if ($canManage) : $evidence = $this->evidence(); ?>
                <details><summary><?php esc_html_e('Record the evidence basis', 'help4-builder-suite'); ?></summary>
                    <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
                        <input type="hidden" name="action" value="h4bs_save_form_timestamp_evidence">
                        <input type="hidden" name="revision" value="<?php echo esc_attr((string) $evidence['revision']); ?>">
                        <input type="hidden" name="request_id" value="<?php echo esc_attr(wp_generate_uuid4()); ?>">
                        <?php wp_nonce_field('h4bs_save_form_timestamp_evidence'); ?>
                        <label><?php esc_html_e('How were older database dates stored?', 'help4-builder-suite'); ?>
                            <select name="basis" required>
                                <option value="unresolved" <?php selected($evidence['basis'], 'unresolved'); ?>><?php esc_html_e('Not verified yet', 'help4-builder-suite'); ?></option>
                                <option value="utc" <?php selected($evidence['basis'], 'utc'); ?>><?php esc_html_e('UTC', 'help4-builder-suite'); ?></option>
                                <option value="fixed_offset" <?php selected($evidence['basis'], 'fixed_offset'); ?>><?php esc_html_e('Fixed UTC offset', 'help4-builder-suite'); ?></option>
                                <option value="named_timezone" <?php selected($evidence['basis'], 'named_timezone'); ?>><?php esc_html_e('Named timezone with daylight saving', 'help4-builder-suite'); ?></option>
                            </select>
                        </label>
                        <label><?php esc_html_e('Offset minutes (fixed-offset only)', 'help4-builder-suite'); ?> <input type="number" min="-840" max="840" name="offset_minutes" value="<?php echo esc_attr((string) $evidence['offset_minutes']); ?>"></label>
                        <label><?php esc_html_e('Timezone name (named-timezone only)', 'help4-builder-suite'); ?> <input type="text" name="timezone" maxlength="80" value="<?php echo esc_attr((string) $evidence['timezone']); ?>" placeholder="America/Detroit"></label>
                        <label><?php esc_html_e('Evidence note', 'help4-builder-suite'); ?> <textarea name="note" rows="3" maxlength="500" required><?php echo esc_textarea((string) $evidence['note']); ?></textarea><span class="description"><?php esc_html_e('Record where the basis was verified. Do not include visitor names, email addresses, or message answers.', 'help4-builder-suite'); ?></span></label>
                        <button class="button button-primary" type="submit"><?php esc_html_e('Save evidence and refresh preview', 'help4-builder-suite'); ?></button>
                    </form>
                </details>
            <?php endif; ?>
            <p class="description"><?php esc_html_e('This release deliberately provides no Apply or automatic migration control. A later migration requires a fresh fingerprint, exact export acknowledgement, transactional rollback proof, and explicit confirmation.', 'help4-builder-suite'); ?></p>
        </section>
        <?php
    }

    public function save(): void
    {
        if (! current_user_can('manage_help4_form_privacy')) {
            wp_die(esc_html__('You cannot change timestamp evidence.', 'help4-builder-suite'), '', ['response' => 403]);
        }
        check_admin_referer('h4bs_save_form_timestamp_evidence');
        $basis = sanitize_key((string) wp_unslash($_POST['basis'] ?? ''));
        $revision = sanitize_text_field((string) wp_unslash($_POST['revision'] ?? ''));
        $requestId = sanitize_text_field((string) wp_unslash($_POST['request_id'] ?? ''));
        $note = sanitize_textarea_field((string) wp_unslash($_POST['note'] ?? ''));
        $offset = (int) sanitize_text_field((string) wp_unslash($_POST['offset_minutes'] ?? '0'));
        $timezone = sanitize_text_field((string) wp_unslash($_POST['timezone'] ?? ''));
        if (! wp_is_uuid($requestId, 4) || ! in_array($basis, ['unresolved', 'utc', 'fixed_offset', 'named_timezone'], true) || mb_strlen(trim($note)) < 3 || $offset < -840 || $offset > 840) {
            wp_die(esc_html__('The evidence record is incomplete. Nothing was saved.', 'help4-builder-suite'), '', ['response' => 400]);
        }
        if ($basis === 'named_timezone') {
            try { new \DateTimeZone($timezone); } catch (\Exception $exception) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
                wp_die(esc_html__('Choose a valid named timezone. Nothing was saved.', 'help4-builder-suite'), '', ['response' => 400]);
            }
        }
        $current = $this->evidence();
        $payload = hash('sha256', (string) wp_json_encode([$basis, $offset, $timezone, $note, $revision]));
        if ((string) $current['last_request_id'] === $requestId) {
            if (hash_equals((string) $current['last_payload_hash'], $payload)) {
                $this->redirect();
            }
            wp_die(esc_html__('That request identifier was already used for different evidence.', 'help4-builder-suite'), '', ['response' => 409]);
        }
        if (! hash_equals((string) $current['revision'], $revision)) {
            wp_die(esc_html__('A newer evidence record exists. Reload and review it; nothing was overwritten.', 'help4-builder-suite'), '', ['response' => 409]);
        }
        $value = ['basis' => $basis, 'offset_minutes' => $offset, 'timezone' => $timezone, 'note' => $note, 'revision_token' => wp_generate_uuid4(), 'updated_at' => current_time('mysql'), 'updated_by' => get_current_user_id(), 'last_request_id' => $requestId, 'last_payload_hash' => $payload];
        $raw = get_option(self::OPTION, null);
        $saved = $raw === null ? add_option(self::OPTION, $value, '', false) : update_option(self::OPTION, $value, false);
        if (! $saved && get_option(self::OPTION) !== $value) {
            wp_die(esc_html__('Timestamp evidence could not be saved. Nothing else changed.', 'help4-builder-suite'), '', ['response' => 500]);
        }
        $this->redirect();
    }

    /** @param array<string,mixed> $policy @return array<string,mixed> */
    private function readiness(array $policy): array
    {
        global $wpdb;
        $table = $wpdb->prefix . 'h4_form_entries';
        $known = $wpdb->get_var($wpdb->prepare('SELECT COUNT(*) FROM %i WHERE timestamp_basis = %s', $table, 'utc')); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Exact admin-only readiness count from the dedicated Forms table.
        $legacy = $wpdb->get_var($wpdb->prepare('SELECT COUNT(*) FROM %i WHERE timestamp_basis <> %s OR timestamp_basis IS NULL', $table, 'utc')); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Exact admin-only readiness count from the dedicated Forms table.
        if ($known === null || $legacy === null || $wpdb->last_error !== '') {
            return ['valid' => false, 'known' => 0, 'legacy' => 0, 'error' => __('Timestamp readiness could not be read. Do not rely on age-based results.', 'help4-builder-suite')];
        }
        $evidence = $this->evidence();
        $result = ['valid' => true, 'known' => (int) $known, 'legacy' => (int) $legacy, 'basis' => $evidence['basis'], 'basis_label' => '', 'delta_label' => '', 'sample' => []];
        if ((int) $legacy === 0 || $evidence['basis'] === 'unresolved') { return $result; }
        if ((int) $legacy > self::MAX_SCAN) {
            $result['valid'] = false;
            $result['error'] = __('More than 10,000 older records need review. Narrow this work outside the live request; no partial delta is shown.', 'help4-builder-suite');
            return $result;
        }
        $rows = $wpdb->get_results($wpdb->prepare('SELECT id, form_id, created_at FROM %i WHERE timestamp_basis <> %s OR timestamp_basis IS NULL ORDER BY id ASC', $table, 'utc'), ARRAY_A); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Bounded preview reads the exact dedicated-table set once.
        if (! is_array($rows) || count($rows) !== (int) $legacy || $wpdb->last_error !== '') {
            $result['valid'] = false; $result['error'] = __('Stored messages changed while readiness was being calculated. Refresh before relying on it.', 'help4-builder-suite'); return $result;
        }
        $cutoff = (string) ($policy['cutoff'] ?? ''); $before = 0; $after = 0; $sample = [];
        foreach ($rows as $row) {
            $stored = (string) $row['created_at']; $utc = $this->asUtc($stored, $evidence);
            if ($utc === null) { $result['valid'] = false; $result['error'] = __('At least one older date is ambiguous or invalid for the recorded basis. Keep it held or excluded and review offline.', 'help4-builder-suite'); return $result; }
            if ($cutoff !== '' && $stored < $cutoff) { ++$before; }
            if ($cutoff !== '' && $utc < $cutoff) { ++$after; }
            if (count($sample) < 10) { $sample[] = ['id' => (int) $row['id'], 'form_id' => (int) $row['form_id'], 'stored' => $stored, 'utc' => $utc]; }
        }
        $result['basis_label'] = $this->basisLabel($evidence);
        /* translators: 1: count under stored timestamps, 2: count under the evidence-based UTC preview. */
        $result['delta_label'] = sprintf(__('For the frozen draft cutoff, %1$d older records currently compare before it and %2$d would compare before it under this evidence.', 'help4-builder-suite'), $before, $after);
        $result['sample'] = $sample;
        return $result;
    }

    /** @param array<string,mixed> $evidence */
    private function asUtc(string $stored, array $evidence): ?string
    {
        try {
            if ($evidence['basis'] === 'utc') { $zone = new \DateTimeZone('UTC'); }
            elseif ($evidence['basis'] === 'fixed_offset') { $minutes = (int) $evidence['offset_minutes']; $zone = new \DateTimeZone(sprintf('%s%02d:%02d', $minutes < 0 ? '-' : '+', intdiv(abs($minutes), 60), abs($minutes) % 60)); }
            else {
                $zone = new \DateTimeZone((string) $evidence['timezone']);
                $wallClock = \DateTimeImmutable::createFromFormat('!Y-m-d H:i:s', $stored, new \DateTimeZone('UTC'));
                if (! $wallClock) { return null; }
                $wallEpoch = $wallClock->getTimestamp();
                $candidates = [];
                foreach ($zone->getTransitions($wallEpoch - 172800, $wallEpoch + 172800) as $transition) {
                    $candidate = $wallEpoch - (int) $transition['offset'];
                    if ((new \DateTimeImmutable('@' . $candidate))->setTimezone($zone)->format('Y-m-d H:i:s') === $stored) {
                        $candidates[$candidate] = true;
                    }
                }
                // A skipped or repeated local clock time cannot be transformed without guessing.
                if (count($candidates) !== 1) { return null; }
                return gmdate('Y-m-d H:i:s', (int) array_key_first($candidates));
            }
            $date = \DateTimeImmutable::createFromFormat('!Y-m-d H:i:s', $stored, $zone);
            if (! $date || $date->format('Y-m-d H:i:s') !== $stored) { return null; }
            return $date->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i:s');
        } catch (\Exception $exception) { return null; }
    }

    /** @return array<string,mixed> */
    private function evidence(): array
    {
        $raw = get_option(self::OPTION, []); $raw = is_array($raw) ? $raw : [];
        $value = array_merge(['basis' => 'unresolved', 'offset_minutes' => 0, 'timezone' => '', 'note' => '', 'revision_token' => '', 'updated_at' => '', 'updated_by' => 0, 'last_request_id' => '', 'last_payload_hash' => ''], $raw);
        $value['revision'] = hash('sha256', (string) wp_json_encode([$value['basis'], $value['offset_minutes'], $value['timezone'], $value['note'], $value['revision_token']]));
        return $value;
    }

    /** @param array<string,mixed> $evidence */
    private function basisLabel(array $evidence): string
    {
        if ($evidence['basis'] === 'utc') { return __('Recorded evidence says older dates were UTC.', 'help4-builder-suite'); }
        if ($evidence['basis'] === 'fixed_offset') {
            /* translators: %d: signed number of minutes from UTC. */
            return sprintf(__('Recorded evidence uses a fixed offset of %d minutes from UTC.', 'help4-builder-suite'), (int) $evidence['offset_minutes']);
        }
        /* translators: %s: IANA timezone name, for example America/Detroit. */
        return sprintf(__('Recorded evidence uses timezone %s; daylight-saving transitions are evaluated per stored date.', 'help4-builder-suite'), (string) $evidence['timezone']);
    }

    private function redirect(): void
    {
        wp_safe_redirect(admin_url('admin.php?page=h4bs-form-privacy#h4bs-timestamp-readiness-title'));
        exit;
    }
}
