<?php
/**
 * Session aggregator — runs hourly via cron, builds wp_seoforge_sessions
 * from raw wp_seoforge_visits rows.
 *
 * A session = all pageviews sharing the same first-party session_id cookie
 * (which expires when the browser closes). Bounce = session with exactly 1
 * pageview.
 *
 * @package Brainwerk SEO Suite
 */

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

class Seoforge_Sessions {

    public static function register( Seoforge_Loader $loader ) {
        $loader->add_action( 'seoforge_aggregate_sessions', __CLASS__, 'aggregate' );
        $loader->add_action( 'seoforge_cleanup_visits', __CLASS__, 'cleanup_old_sessions' );
    }

    /**
     * Build/update sessions from ALL visit rows (full rescan every run, not
     * an incremental pass since the last run). Idempotent — re-running won't
     * double-count, since existing sessions are updated in place by
     * session_id rather than re-inserted.
     *
     * TODO(perf): a watermark-bounded incremental pass would avoid re-scanning
     * the whole visits table every hour; left as a future optimization.
     */
    public static function aggregate() {
        global $wpdb;
        $visits   = Seoforge_Utils::table( 'visits' );
        $sessions = Seoforge_Utils::table( 'sessions' );

        $rows = $wpdb->get_results( $wpdb->prepare( "SELECT session_id, MIN(visitor_hash) AS visitor_hash, MIN(blog_id) AS blog_id,
                MIN(created_at) AS started_at,
                MAX(created_at) AS ended_at,
                COUNT(*)        AS pageviews,
                MIN(source_type) AS source_type,
                MIN(source_name) AS source_name,
                MIN(country)     AS country,
                MIN(region)      AS region,
                MIN(city)        AS city,
                MIN(device_type) AS device_type,
                SUBSTRING_INDEX(GROUP_CONCAT(url ORDER BY created_at ASC SEPARATOR '\\n'), '\\n', 1)  AS entry_url,
                SUBSTRING_INDEX(GROUP_CONCAT(url ORDER BY created_at DESC SEPARATOR '\\n'), '\\n', 1) AS exit_url
            FROM %i
            WHERE is_bot = 0 AND session_id <> ''
            GROUP BY session_id", $visits ) );

        if ( empty( $rows ) ) {
            return 0;
        }

        $count = 0;
        foreach ( $rows as $r ) {
            $duration = max( 0, strtotime( $r->ended_at ) - strtotime( $r->started_at ) );
            $bounce   = ( (int) $r->pageviews === 1 ) ? 1 : 0;

            $existing_id = (int) $wpdb->get_var( $wpdb->prepare(
                "SELECT id FROM %i WHERE session_id = %s LIMIT 1",
                $sessions,
                $r->session_id
            ) );

            $data = array(
                'session_id'   => $r->session_id,
                'visitor_hash' => (string) $r->visitor_hash,
                'blog_id'      => (int) $r->blog_id,
                'started_at'   => $r->started_at,
                'ended_at'     => $r->ended_at,
                'pageviews'    => (int) $r->pageviews,
                'entry_url'    => mb_substr( (string) $r->entry_url, 0, 500 ),
                'exit_url'     => mb_substr( (string) $r->exit_url, 0, 500 ),
                'source_type'  => (string) $r->source_type,
                'source_name'  => mb_substr( (string) $r->source_name, 0, 60 ),
                'country'      => (string) $r->country,
                'region'       => null !== $r->region ? (string) $r->region : null,
                'city'         => null !== $r->city ? (string) $r->city : null,
                'device_type'  => (string) $r->device_type,
                'duration_sec' => $duration,
                'is_bounce'    => $bounce,
            );
            if ( $existing_id ) {
                $wpdb->update( $sessions, $data, array( 'id' => $existing_id ) );
            } else {
                $wpdb->insert( $sessions, $data );
            }
            $count++;
        }
        return $count;
    }

    /**
     * Cleanup cron — scoped to one blog (defaults to the current one) so a
     * Multisite cleanup never touches another site's rows — see
     * Seoforge_Tools::cleanup_now().
     */
    public static function cleanup_old_sessions( $blog_id = null ) {
        global $wpdb;
        $blog_id = null === $blog_id ? Seoforge_Utils::current_blog_id() : (int) $blog_id;
        $days = max( 1, (int) Seoforge_Settings::get( 'session_retention_days', 730 ) );
        $cutoff = gmdate( 'Y-m-d H:i:s', time() - DAY_IN_SECONDS * $days );
        return $wpdb->query( $wpdb->prepare( "DELETE FROM %i WHERE blog_id = %d AND ended_at < %s", Seoforge_Utils::table( 'sessions' ), $blog_id, $cutoff ) );
    }
}
