<?php
/**
 * Daily retention cron — deletes synced rows older than 14 days, plus a
 * 30-day hard cap on unsynced rows so a wpdeploy outage doesn't fill the
 * database forever. The hard-cap branch fires a CRITICAL alert because
 * deleting unsynced rows means data was lost.
 *
 * Tables purged (event streams only — running aggregates are NOT touched):
 *   - wp_af_stats_pageviews
 *   - wp_af_stats_sessions
 *   - wp_af_stats_searches
 *   - wp_af_stats_outbound
 *   - wp_af_user_recent_activity
 *
 * NOT touched: wp_af_user_interests (running totals), wp_af_lot_keywords
 * (reference data), wp_af_stats_visitor_emails (identity map),
 * wp_af_bid_events (bidder identity).
 *
 * On wp_af_bid_events specifically: it was purged here until 3.7.2.3, which was
 * wrong on two counts. It is a low-volume identity table (hundreds to low
 * thousands of rows per site — one per bidder per lot), not an event stream, so
 * it carries no storage risk. And because rows can be replayed from historical
 * Bidspirit webhooks, the 30-day ceiling deleted a freshly-restored backfill on
 * the next daily run while counting those rows as "unsynced at ceiling" — which
 * fired a CRITICAL retention-overrun incident to Telegram/wpdeploy claiming sync
 * had been broken for a month, when in fact an operator had simply pressed
 * "Backfill bidders".
 */

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

class AF_Data_Purge {

    const FLOOR_DAYS = 14;
    const CEILING_DAYS = 30;

    const HOOK = 'auctionforge_purge_synced_data';

    public static function register() {
        add_action(self::HOOK, array(__CLASS__, 'run'));
        if (!wp_next_scheduled(self::HOOK)) {
            wp_schedule_event(time() + 600, 'daily', self::HOOK);
        }
    }

    public static function run() {
        global $wpdb;

        $tables = array(
            // table => date column used for the 30-day ceiling
            'af_stats_pageviews'        => 'created_at',
            'af_stats_sessions'         => 'started_at',
            'af_stats_searches'         => 'created_at',
            'af_stats_outbound'         => 'created_at',
            'af_user_recent_activity'   => 'occurred_at',
            // af_bid_events deliberately absent — see the class docblock.
        );

        $totals = array('floor_deleted' => 0, 'ceiling_deleted' => 0, 'unsynced_at_ceiling' => 0);

        foreach ($tables as $tbl => $date_col) {
            $full = $wpdb->prefix . $tbl;
            if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $full)) !== $full) continue;

            // Has the synced_at column?
            $has_synced_at = (bool) $wpdb->get_var($wpdb->prepare(
                "SELECT 1 FROM information_schema.columns
                  WHERE table_schema = %s AND table_name = %s AND column_name = 'synced_at'",
                DB_NAME, $full
            ));

            if ($has_synced_at) {
                // Floor: 14 days post-confirmation.
                $floor_cut = gmdate('Y-m-d H:i:s', time() - self::FLOOR_DAYS * 86400);
                $deleted = $wpdb->query($wpdb->prepare(
                    "DELETE FROM $full WHERE synced_at IS NOT NULL AND synced_at < %s",
                    $floor_cut
                ));
                $totals['floor_deleted'] += (int) $deleted;
            }

            // Ceiling: 30-day cap on creation. Counts unsynced kills first.
            $ceil_cut = gmdate('Y-m-d H:i:s', time() - self::CEILING_DAYS * 86400);
            if ($has_synced_at) {
                $unsynced = (int) $wpdb->get_var($wpdb->prepare(
                    "SELECT COUNT(*) FROM $full WHERE synced_at IS NULL AND $date_col < %s",
                    $ceil_cut
                ));
                $totals['unsynced_at_ceiling'] += $unsynced;
            }
            $deleted = $wpdb->query($wpdb->prepare(
                "DELETE FROM $full WHERE $date_col < %s",
                $ceil_cut
            ));
            $totals['ceiling_deleted'] += (int) $deleted;
        }

        // If we deleted rows that were never synced, alert loudly. Reuse the
        // existing sync-error fan-out so it ends up in Telegram + wpdeploy
        // sync_incidents in the same way real sync errors do.
        if ($totals['unsynced_at_ceiling'] > 0) {
            $msg = sprintf(
                'Retention overrun: %d rows older than %d days were deleted before being synced to wpdeploy. Sync has been broken for at least 30 days.',
                $totals['unsynced_at_ceiling'],
                self::CEILING_DAYS
            );
            do_action(
                'auctionforge_sync_error_logged',
                0,                  // af_log_id
                'retention_overrun', // context
                $msg,                // message
                null,                // auctionId
                null                 // jobId
            );
        }

        update_option('auctionforge_last_purge_at', current_time('mysql', true), 'no');
        update_option('auctionforge_last_purge_totals', wp_json_encode($totals), 'no');
    }
}
