<?php
/**
 * AuctionForge Site Statistics — Scheduled weekly email report.
 *
 * Sends a plain summary of the last 7 days (vs the previous 7) to a chosen
 * recipient, once a week on a configured weekday. Driven by a daily cron that
 * only sends on the chosen day, so it does not rely on WP's `weekly` schedule
 * (added in WP 5.4; the plugin supports 5.2+).
 *
 * Disabled by default — no site sends mail until an admin opts in under
 * Site Statistics → Settings.
 */

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

class AF_Stats_Report {

    const HOOK = 'af_stats_weekly_report';
    const OPT_ENABLED = 'af_stats_weekly_email';
    const OPT_TO = 'af_stats_weekly_email_to';
    const OPT_DAY = 'af_stats_weekly_email_day';     // 1 (Mon) … 7 (Sun), ISO-8601
    const OPT_LAST = 'af_stats_weekly_email_last';   // YYYY-MM-DD of last send (dedupe)

    public static function register() {
        add_action(self::HOOK, array(__CLASS__, 'maybe_send'));
        if (!wp_next_scheduled(self::HOOK)) {
            wp_schedule_event(time(), 'daily', self::HOOK);
        }
        // Admin "send test now" button.
        add_action('wp_ajax_af_stats_send_test_report', array(__CLASS__, 'handle_send_test'));
    }

    /** Daily cron entry point — only sends on the configured weekday. */
    public static function maybe_send() {
        if (get_option(self::OPT_ENABLED, 'off') !== 'on') return;

        $day = max(1, min(7, intval(get_option(self::OPT_DAY, 1))));
        if (intval(current_time('N')) !== $day) return;

        // Guard against multiple sends on the same day (cron can fire repeatedly).
        $today = current_time('Y-m-d');
        if (get_option(self::OPT_LAST) === $today) return;
        update_option(self::OPT_LAST, $today, 'no');

        self::send_report();
    }

    /**
     * Parse a recipient string or array into a deduped list of valid emails.
     * Accepts a single address or several separated by commas, semicolons,
     * whitespace or newlines.
     * @return string[]
     */
    public static function parse_recipients($raw) {
        if (is_array($raw)) {
            $raw = implode(',', $raw);
        }
        $parts = preg_split('/[,;\s]+/', (string) $raw, -1, PREG_SPLIT_NO_EMPTY);
        $valid = array();
        foreach ((array) $parts as $part) {
            $email = sanitize_email(trim($part));
            if ($email && is_email($email)) {
                $valid[strtolower($email)] = $email; // dedupe, case-insensitive
            }
        }
        return array_values($valid);
    }

    /** Build and send the report. $override_to may be a single address, a
     *  separated list, or an array; null uses the saved recipient option. */
    public static function send_report($override_to = null) {
        $recipients = self::parse_recipients(
            $override_to !== null ? $override_to : get_option(self::OPT_TO, get_option('admin_email'))
        );
        if (empty($recipients)) {
            $admin = get_option('admin_email');
            if ($admin && is_email($admin)) {
                $recipients = array($admin);
            }
        }
        if (empty($recipients)) {
            return new \WP_Error('af_no_recipient', 'No valid recipient configured.');
        }

        $to_dt = current_time('Y-m-d') . ' 23:59:59';
        $from_dt = date('Y-m-d', strtotime('-6 days', strtotime(current_time('Y-m-d')))) . ' 00:00:00';
        $prev_to_dt = date('Y-m-d H:i:s', strtotime($from_dt) - 1);
        $prev_from_dt = date('Y-m-d H:i:s', strtotime($from_dt) - 7 * DAY_IN_SECONDS);

        $html = self::build_html($from_dt, $to_dt, $prev_from_dt, $prev_to_dt);
        $site = wp_specialchars_decode(get_bloginfo('name'), ENT_QUOTES);
        $subject = sprintf('[%s] Weekly site statistics — %s to %s', $site, substr($from_dt, 0, 10), substr($to_dt, 0, 10));

        $set_html = function () { return 'text/html'; };
        add_filter('wp_mail_content_type', $set_html);
        $sent = wp_mail($recipients, $subject, $html);
        remove_filter('wp_mail_content_type', $set_html);

        return $sent;
    }

    /** Assemble the HTML body. */
    private static function build_html($from, $to, $prev_from, $prev_to) {
        global $wpdb;
        // Scoped from $prev_from so the week-on-week comparison still sees
        // archived rows once the previous week has aged past the boundary —
        // otherwise every report would show a spurious collapse in traffic.
        $pv = AF_Stats_Archive::table_expr('af_stats_pageviews', 'pv', $prev_from, $to);
        $sess = AF_Stats_Archive::table_expr('af_stats_sessions', 'sess', $prev_from, $to);

        $count = function ($sql, $a, $b) use ($wpdb) {
            return intval($wpdb->get_var($wpdb->prepare($sql, $a, $b)));
        };
        $pageviews = $count("SELECT COUNT(*) FROM $pv WHERE created_at BETWEEN %s AND %s", $from, $to);
        $visitors  = $count("SELECT COUNT(DISTINCT visitor_hash) FROM $pv WHERE created_at BETWEEN %s AND %s", $from, $to);
        $sessions  = $count("SELECT COUNT(*) FROM $sess WHERE started_at BETWEEN %s AND %s", $from, $to);
        $p_pageviews = $count("SELECT COUNT(*) FROM $pv WHERE created_at BETWEEN %s AND %s", $prev_from, $prev_to);
        $p_visitors  = $count("SELECT COUNT(DISTINCT visitor_hash) FROM $pv WHERE created_at BETWEEN %s AND %s", $prev_from, $prev_to);
        $p_sessions  = $count("SELECT COUNT(*) FROM $sess WHERE started_at BETWEEN %s AND %s", $prev_from, $prev_to);

        // Engagement funnel: visitors → viewed a lot → left a bid (this week)
        $lot_viewers = $count("SELECT COUNT(DISTINCT visitor_hash) FROM $pv WHERE created_at BETWEEN %s AND %s AND url_path LIKE '%%/lot-%%'", $from, $to);
        $bidders = 0;
        $bids_measured = false;
        $bid_has_alerts = false;
        $bid_table = $wpdb->prefix . 'af_bid_events';
        if ($wpdb->get_var("SHOW TABLES LIKE '$bid_table'") === $bid_table) {
            $bidders = intval($wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(DISTINCT user_email) FROM $bid_table WHERE occurred_at BETWEEN %s AND %s", $from, $to
            )));
            // Mirrors the dashboard: never assert a zero for a metric that
            // simply has no data source on this site.
            $bids_measured = $bidders > 0;
            if ($bids_measured && $wpdb->get_var($wpdb->prepare(
                "SELECT 1 FROM information_schema.columns
                  WHERE table_schema = %s AND table_name = %s AND column_name = 'source'",
                DB_NAME, $bid_table
            ))) {
                $bid_has_alerts = (bool) $wpdb->get_var($wpdb->prepare(
                    "SELECT EXISTS(SELECT 1 FROM $bid_table
                      WHERE occurred_at BETWEEN %s AND %s AND source = 'alert')", $from, $to
                ));
            }
        }

        // Busiest hour for every weekday (Mon–Sun)
        $names = array(1 => 'Sunday', 2 => 'Monday', 3 => 'Tuesday', 4 => 'Wednesday', 5 => 'Thursday', 6 => 'Friday', 7 => 'Saturday');
        $grid = $wpdb->get_results($wpdb->prepare(
            "SELECT DAYOFWEEK(created_at) dow, HOUR(created_at) h, COUNT(*) c
             FROM $pv WHERE created_at BETWEEN %s AND %s GROUP BY dow, h", $from, $to
        ));
        $peak = array();
        foreach ((array) $grid as $g) {
            $dw = (int) $g->dow;
            if (!isset($peak[$dw]) || (int) $g->c > $peak[$dw]['c']) {
                $peak[$dw] = array('h' => (int) $g->h, 'c' => (int) $g->c);
            }
        }

        // Most-viewed individual lot pages (the items) — the useful signal.
        // Home, catalogue listings/pagination, profile and other non-lot pages
        // are excluded as low-signal "vanity" hits.
        $top_pages = $wpdb->get_results($wpdb->prepare(
            "SELECT url_path, page_title, COUNT(*) views FROM $pv
             WHERE created_at BETWEEN %s AND %s
               AND url_path LIKE '%%/lot-%%'
             GROUP BY url_path ORDER BY views DESC LIMIT 8", $from, $to
        ), ARRAY_A);

        // ── render helpers ──────────────────────────────────────────────
        $fmt = function ($n) { return number_format((int) $n); };

        // Coloured change pill (green up / red down / grey flat)
        $badge = function ($cur, $prev) {
            $pill = 'display:inline-block;padding:3px 9px;border-radius:11px;font-size:12px;font-weight:bold;line-height:1;';
            if ($prev <= 0) {
                return $cur > 0
                    ? '<span style="' . $pill . 'background:#e6f4ea;color:#1e8e3e;">▲ New</span>'
                    : '<span style="' . $pill . 'background:#eef1f4;color:#6b7280;">—</span>';
            }
            $pct = round(($cur - $prev) / $prev * 100);
            if ($pct >= 1000) return '<span style="' . $pill . 'background:#e6f4ea;color:#1e8e3e;">▲ ' . number_format($cur / max(1, $prev), 0) . '×</span>';
            if ($pct > 0)     return '<span style="' . $pill . 'background:#e6f4ea;color:#1e8e3e;">▲ ' . $pct . '%</span>';
            if ($pct < 0)     return '<span style="' . $pill . 'background:#fdecea;color:#c0392b;">▼ ' . abs($pct) . '%</span>';
            return '<span style="' . $pill . 'background:#eef1f4;color:#6b7280;">0%</span>';
        };

        // KPI hero card — one <td> in a 3-column row
        $kpi_card = function ($label, $cur, $prev) use ($fmt, $badge) {
            return '<td width="33.33%" valign="top" style="padding:5px;">'
                . '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f7f9fc;border:1px solid #e7ebf0;border-radius:8px;"><tr><td style="padding:14px 16px;">'
                . '<div style="font-size:11px;letter-spacing:.6px;text-transform:uppercase;color:#6b7280;">' . esc_html($label) . '</div>'
                . '<div style="font-size:25px;font-weight:700;color:#12263f;line-height:1.15;margin:5px 0 7px;">' . $fmt($cur) . '</div>'
                . $badge($cur, $prev)
                . '<div style="font-size:11px;color:#9aa1ab;margin-top:6px;">vs ' . $fmt($prev) . ' last week</div>'
                . '</td></tr></table></td>';
        };

        // Funnel bars (email-safe two-cell table)
        $funnel = array(
            array('Visitors', $visitors, '#2271b1'),
            array('Viewed a lot', $lot_viewers, '#1e8e3e'),
        );
        // Only include the bidder stage when it was actually measured. Emailing
        // a client "Left a bid: 0" for a week in which they sold hundreds of
        // lots is worse than saying nothing.
        if ($bids_measured) {
            $funnel[] = array(
                $bid_has_alerts ? 'Bidders &amp; lot-watchers' : 'Bidders',
                $bidders,
                '#d9822b'
            );
        }
        $funnel_html = '';
        foreach ($funnel as $stg) {
            $pct  = $visitors > 0 ? round($stg[1] / $visitors * 100) : 0;
            $w    = max(1, min(100, $pct));
            $rest = 100 - $w;
            $funnel_html .= '<tr><td style="padding:7px 0;">'
                . '<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>'
                . '<td style="font-size:13px;color:#1d2327;padding-bottom:5px;">' . esc_html($stg[0]) . '</td>'
                . '<td align="right" style="font-size:13px;color:#1d2327;padding-bottom:5px;"><strong>' . $fmt($stg[1]) . '</strong> <span style="color:#9aa1ab;">' . $pct . '%</span></td>'
                . '</tr></table>'
                . '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#eef1f4;border-radius:6px;"><tr>'
                . '<td bgcolor="' . $stg[2] . '" width="' . $w . '%" style="background:' . $stg[2] . ';height:12px;border-radius:6px;font-size:0;line-height:0;">&nbsp;</td>'
                . ($rest > 0 ? '<td width="' . $rest . '%" style="font-size:0;line-height:0;">&nbsp;</td>' : '')
                . '</tr></table>'
                . '</td></tr>';
        }

        // Busiest hour per weekday (Mon–Sun); highlight the busiest day
        $maxDow = 0; $maxC = 0;
        foreach ($peak as $dw => $pk) { if ($pk['c'] > $maxC) { $maxC = $pk['c']; $maxDow = $dw; } }
        $bpd_html = '';
        $z = 0;
        foreach (array(2, 3, 4, 5, 6, 7, 1) as $dw) {
            $isMax = ($dw === $maxDow && $maxC > 0);
            $bg    = $isMax ? '#e6f4ea' : (($z % 2) ? '#ffffff' : '#f7f9fc');
            $day   = esc_html($names[$dw]) . ($isMax ? ' <span style="color:#1e8e3e;font-size:11px;font-weight:bold;">● busiest</span>' : '');
            $hr    = isset($peak[$dw])
                ? ('<strong>' . $peak[$dw]['h'] . ':00</strong> <span style="color:#9aa1ab;">(' . $fmt($peak[$dw]['c']) . ' views)</span>')
                : '<span style="color:#9aa1ab;">no traffic</span>';
            $bpd_html .= '<tr bgcolor="' . $bg . '"><td style="padding:8px 16px;border-bottom:1px solid #eef1f4;font-size:13px;color:#1d2327;">' . $day . '</td>'
                . '<td style="padding:8px 16px;border-bottom:1px solid #eef1f4;font-size:13px;color:#1d2327;">' . $hr . '</td></tr>';
            $z++;
        }

        // Top pages (rank + mini bar, cleaned titles)
        $maxViews = 0;
        foreach ((array) $top_pages as $p) { $maxViews = max($maxViews, (int) $p['views']); }
        $pages_html = '';
        $rank = 1;
        foreach ((array) $top_pages as $p) {
            $v    = (int) $p['views'];
            $w    = $maxViews > 0 ? max(3, round($v / $maxViews * 100)) : 0;
            $rest = 100 - $w;
            $pages_html .= '<tr><td style="padding:9px 16px;border-bottom:1px solid #eef1f4;">'
                . '<div style="font-size:13px;color:#1d2327;"><span style="color:#9aa1ab;">' . $rank . '.</span> ' . esc_html(self::clean_title($p['page_title'], $p['url_path'])) . '</div>'
                . '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin-top:6px;background:#eef1f4;border-radius:4px;"><tr>'
                . '<td bgcolor="#2271b1" width="' . $w . '%" style="background:#2271b1;height:6px;border-radius:4px;font-size:0;line-height:0;">&nbsp;</td>'
                . ($rest > 0 ? '<td width="' . $rest . '%" style="font-size:0;line-height:0;">&nbsp;</td>' : '')
                . '</tr></table>'
                . '</td>'
                . '<td align="right" valign="top" style="padding:9px 16px;border-bottom:1px solid #eef1f4;font-size:14px;font-weight:bold;color:#12263f;white-space:nowrap;">' . $fmt($v) . '</td></tr>';
            $rank++;
        }
        if ($pages_html === '') {
            $pages_html = '<tr><td colspan="2" style="padding:12px 16px;color:#6b7280;font-size:13px;">No data yet</td></tr>';
        }

        $site     = wp_specialchars_decode(get_bloginfo('name'), ENT_QUOTES);
        $d_from   = date_i18n('j M', strtotime($from));
        $d_to     = date_i18n('j M Y', strtotime($to));
        $dash_url = admin_url('edit.php?post_type=lot&page=auctionforge-stats');
        $heading  = function ($text) {
            return '<h2 style="margin:22px 0 10px;font-size:15px;color:#12263f;border-left:3px solid #2271b1;padding-left:9px;">' . esc_html($text) . '</h2>';
        };

        ob_start();
        ?>
<div style="margin:0;padding:0;background:#eef1f5;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#eef1f5;">
  <tr><td align="center" style="padding:24px 12px;">
    <table role="presentation" width="600" cellpadding="0" cellspacing="0" style="width:600px;max-width:600px;background:#ffffff;border-radius:10px;overflow:hidden;border:1px solid #e3e7ec;font-family:Arial,Helvetica,sans-serif;color:#1d2327;">

      <tr><td style="background:#12263f;padding:26px 28px;">
        <div style="font-size:21px;font-weight:700;color:#ffffff;line-height:1.2;"><?php echo esc_html($site); ?></div>
        <div style="font-size:13px;color:#9fc0e0;margin-top:4px;">Weekly statistics &nbsp;&middot;&nbsp; <?php echo esc_html($d_from); ?> &ndash; <?php echo esc_html($d_to); ?></div>
      </td></tr>

      <tr><td style="padding:18px 23px 4px;">
        <table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>
          <?php
          echo $kpi_card('Visitors', $visitors, $p_visitors);
          echo $kpi_card('Pageviews', $pageviews, $p_pageviews);
          echo $kpi_card('Sessions', $sessions, $p_sessions);
          ?>
        </tr></table>
      </td></tr>

      <tr><td style="padding:4px 28px 0;">
        <?php echo $heading('Visitor journey'); ?>
        <table role="presentation" width="100%" cellpadding="0" cellspacing="0"><?php echo $funnel_html; ?></table>
        <div style="font-size:11px;color:#9aa1ab;margin-top:8px;">Share of this week&rsquo;s visitors who viewed a lot, then placed a bid.</div>
      </td></tr>

      <tr><td style="padding:4px 28px 0;">
        <?php echo $heading('Busiest time each day'); ?>
        <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border:1px solid #e7ebf0;border-radius:8px;overflow:hidden;">
          <tr style="background:#f2f5f9;">
            <td style="padding:8px 16px;font-size:11px;letter-spacing:.5px;text-transform:uppercase;color:#6b7280;">Day</td>
            <td style="padding:8px 16px;font-size:11px;letter-spacing:.5px;text-transform:uppercase;color:#6b7280;">Busiest hour</td>
          </tr>
          <?php echo $bpd_html; ?>
        </table>
      </td></tr>

      <tr><td style="padding:4px 28px 4px;">
        <?php echo $heading('Most viewed lots'); ?>
        <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border:1px solid #e7ebf0;border-radius:8px;overflow:hidden;">
          <?php echo $pages_html; ?>
        </table>
      </td></tr>

      <tr><td align="center" style="padding:22px 28px 6px;">
        <table role="presentation" cellpadding="0" cellspacing="0"><tr>
          <td bgcolor="#2271b1" style="background:#2271b1;border-radius:6px;">
            <a href="<?php echo esc_url($dash_url); ?>" style="display:inline-block;padding:11px 22px;font-size:14px;color:#ffffff;text-decoration:none;font-weight:bold;">View full dashboard &rarr;</a>
          </td>
        </tr></table>
      </td></tr>

      <tr><td style="padding:18px 28px 24px;border-top:1px solid #eef1f4;">
        <div style="font-size:12px;color:#9aa1ab;line-height:1.5;">
          Sent by <strong style="color:#6b7280;">AuctionForge</strong> &middot; <?php echo esc_html($site); ?><br>
          Manage or turn off this report under <span style="color:#6b7280;">Site Statistics &rarr; Settings</span>.
        </div>
      </td></tr>

    </table>
  </td></tr>
</table>
</div>
        <?php
        return ob_get_clean();
    }

    /**
     * Friendlier page title for the report: derive from the URL when blank and
     * strip a trailing " | Site" / " - Site" suffix. Not truncated — the full
     * title is shown so near-identical pages can be told apart.
     */
    private static function clean_title($title, $url_path) {
        $title = trim((string) $title);
        if ($title === '') {
            $path = parse_url((string) $url_path, PHP_URL_PATH);
            $seg = trim((string) (($path !== null && $path !== false) ? $path : $url_path), '/');
            $pos = strrpos($seg, '/');
            if ($pos !== false) $seg = substr($seg, $pos + 1);
            $title = $seg !== '' ? ucwords(str_replace(array('-', '_'), ' ', $seg)) : 'Home';
        }
        $site = get_bloginfo('name');
        if ($site !== '') {
            $title = preg_replace('/\s*[|\x{2013}\x{2014}\-]\s*' . preg_quote($site, '/') . '\s*$/u', '', $title);
            $title = trim((string) $title);
        }
        return $title;
    }

    /** Admin AJAX: send a test copy now. Uses the address currently typed in the
     *  Recipient box (so it works before Save); falls back to the saved option. */
    public static function handle_send_test() {
        if (!current_user_can('manage_options') || !check_ajax_referer('af_stats_admin', 'nonce', false)) {
            wp_send_json_error('Unauthorized');
            return;
        }
        $recipients = self::parse_recipients(isset($_POST['to']) ? wp_unslash($_POST['to']) : '');
        if (empty($recipients)) {
            $recipients = self::parse_recipients(get_option(self::OPT_TO, get_option('admin_email')));
        }
        if (empty($recipients)) {
            wp_send_json_error('No valid recipient address.');
            return;
        }
        $result = self::send_report($recipients);
        if (is_wp_error($result) || !$result) {
            wp_send_json_error('Could not send (check the address(es) and the site mailer).');
            return;
        }
        wp_send_json_success(array('message' => 'Test report sent to ' . implode(', ', $recipients)));
    }
}
