<?php

/**
 * Video Analytics — tracking enqueue, AJAX ingest, settings, REST, queries.
 *
 * Wistia-grade engagement insight for the videos sites already embed
 * (YouTube / Vimeo / self-hosted <video>), without changing hosting, tied to
 * the session/visitor/conversion data Kantan already owns. The client module
 * (assets/js/video-tracking.js) normalizes all three providers into one event
 * stream: impression / play / progress / complete. Engagement is accumulated
 * client-side into 100 duration-relative buckets and arrives as compact deltas
 * on pause/hidden/ended — never per second.
 *
 * Free tier (this plugin): detection, plays, play rate, average watch time,
 * completion rate, the Video dashboard tab. The engagement data still accrues
 * here so the retention/rewatch curves and conversion attribution light up the
 * moment Kantan SEO is activated.
 */

// Exit if accessed directly
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Video tracking settings with defaults. Stored as one option so the
 * front-end enqueue costs a single (autoloaded) lookup.
 */
function kantan_video_get_settings() {
    $defaults = array(
        'enabled'          => true,
        'youtube'          => true,
        'vimeo'            => true,
        'html5'            => true,
        'nocookie'         => true,
        'exclude_autoplay' => true,
    );
    $saved = get_option('kantan_video_settings', array());
    if (!is_array($saved)) {
        $saved = array();
    }
    return array_merge($defaults, array_intersect_key($saved, $defaults));
}

/**
 * Enqueue the video tracking module alongside the main tracker — same
 * logged-in-editor skip, same localized-config pattern.
 */
function kantan_video_enqueue_tracking_script() {
    if (current_user_can('edit_posts')) {
        return;
    }

    $settings = kantan_video_get_settings();
    if (!$settings['enabled']) {
        return;
    }

    wp_enqueue_script(
        'kantan-video',
        KANTAN_ANALYTICS_PLUGIN_URL . 'assets/js/video-tracking.js',
        array(),
        KANTAN_ANALYTICS_VERSION,
        true
    );

    wp_localize_script('kantan-video', 'kantanVideoConfig', array(
        'ajaxUrl'         => admin_url('admin-ajax.php'),
        'providers'       => array(
            'youtube' => (bool) $settings['youtube'],
            'vimeo'   => (bool) $settings['vimeo'],
            'html5'   => (bool) $settings['html5'],
        ),
        'nocookie'        => (bool) $settings['nocookie'],
        'excludeAutoplay' => (bool) $settings['exclude_autoplay'],
    ));
}
add_action('wp_enqueue_scripts', 'kantan_video_enqueue_tracking_script');

/**
 * AJAX ingest for the normalized video event stream.
 *
 * Accepts a JSON batch of up to 20 events. nopriv + cached-page compatible
 * like the other trackers (hardened sanitization, not a nonce, is the
 * safeguard). Raw rows land in wp_kantan_video_events; bucket deltas fold
 * into the durable wp_kantan_video_engagement aggregate; wp_kantan_videos
 * keeps the denormalized per-video counters the list view reads.
 */
function kantan_track_video() {
    if (kantan_is_bot() || kantan_is_excluded_ip()) {
        wp_send_json_success();
        return;
    }

    $settings = kantan_video_get_settings();
    if (!$settings['enabled']) {
        wp_send_json_success();
        return;
    }

    $session_id = sanitize_text_field(wp_unslash($_POST['session_id'] ?? ''));
    $visitor_id = sanitize_text_field(wp_unslash($_POST['visitor_id'] ?? ''));
    if (empty($session_id)) {
        wp_send_json_error('Missing session_id');
        return;
    }

    $events = json_decode(wp_unslash($_POST['events'] ?? '[]'), true);
    if (!is_array($events) || empty($events)) {
        wp_send_json_success();
        return;
    }

    global $wpdb;
    $events_table     = $wpdb->prefix . 'kantan_video_events';
    $engagement_table = $wpdb->prefix . 'kantan_video_engagement';
    $videos_table     = $wpdb->prefix . 'kantan_videos';
    $property_id      = kantan_get_property_id();

    $allowed_types     = array('impression' => 1, 'play' => 1, 'progress' => 1, 'complete' => 1);
    $allowed_providers = array('youtube' => 1, 'vimeo' => 1, 'html5' => 1);

    $count = 0;
    foreach ($events as $event) {
        if (!is_array($event) || ++$count > 20) {
            break;
        }
        $type     = sanitize_text_field($event['type'] ?? '');
        $provider = sanitize_text_field($event['provider'] ?? '');
        $video_key = sanitize_text_field(substr((string) ($event['video_key'] ?? ''), 0, 191));
        if (!isset($allowed_types[$type]) || !isset($allowed_providers[$provider]) || $video_key === '') {
            continue;
        }

        $position = max(0, intval($event['position'] ?? 0));
        $duration = max(0, intval($event['duration'] ?? 0));
        $watch    = ($type === 'progress') ? min(max(0, intval($event['watch'] ?? 0)), 1800) : 0;
        $autoplay = !empty($event['autoplay']) ? 1 : 0;
        $title    = sanitize_text_field(substr((string) ($event['video_title'] ?? ''), 0, 255));
        $page_url = esc_url_raw(substr((string) ($event['page_url'] ?? ''), 0, 500));

        $wpdb->insert(
            $events_table,
            array(
                'property_id'   => $property_id,
                'session_id'    => $session_id,
                'visitor_id'    => $visitor_id,
                'provider'      => $provider,
                'video_key'     => $video_key,
                'page_url'      => $page_url,
                'event_type'    => $type,
                'position'      => $position,
                'duration'      => $duration,
                'watch_seconds' => $watch,
                'autoplay'      => $autoplay,
            )
        );

        // Registry upsert: create the video row on first sight, then fold this
        // event into the denormalized counters the list view reads.
        $wpdb->query($wpdb->prepare(
            "INSERT INTO $videos_table
                (property_id, video_key, provider, video_title, duration,
                 total_impressions, total_plays, total_completes, total_watch_seconds)
             VALUES (%s, %s, %s, %s, %d, %d, %d, %d, %d)
             ON DUPLICATE KEY UPDATE
                video_title = IF(VALUES(video_title) <> '', VALUES(video_title), video_title),
                duration = IF(VALUES(duration) > 0, VALUES(duration), duration),
                total_impressions = total_impressions + VALUES(total_impressions),
                total_plays = total_plays + VALUES(total_plays),
                total_completes = total_completes + VALUES(total_completes),
                total_watch_seconds = total_watch_seconds + VALUES(total_watch_seconds),
                last_seen = CURRENT_TIMESTAMP",
            $property_id,
            $video_key,
            $provider,
            $title,
            $duration,
            ($type === 'impression') ? 1 : 0,
            ($type === 'play') ? 1 : 0,
            ($type === 'complete') ? 1 : 0,
            $watch
        ));

        // Engagement buckets (progress events only). reached is a list of
        // buckets this session newly entered — the client dedupes per session,
        // so a +1 per row aggregates to "sessions that reached bucket i".
        // plays is bucket -> times-played for the rewatch heatmap.
        if ($type === 'progress') {
            $reached = isset($event['reached']) && is_array($event['reached']) ? $event['reached'] : array();
            $plays   = isset($event['plays']) && is_array($event['plays']) ? $event['plays'] : array();

            $rows = array(); // bucket => array(reached, plays)
            foreach (array_slice($reached, 0, 100) as $bucket) {
                $bucket = intval($bucket);
                if ($bucket >= 0 && $bucket <= 99) {
                    $rows[$bucket] = array(1, 0);
                }
            }
            $i = 0;
            foreach ($plays as $bucket => $times) {
                if (++$i > 100) {
                    break;
                }
                $bucket = intval($bucket);
                $times  = min(max(0, intval($times)), 3600);
                if ($bucket < 0 || $bucket > 99 || $times === 0) {
                    continue;
                }
                if (!isset($rows[$bucket])) {
                    $rows[$bucket] = array(0, 0);
                }
                $rows[$bucket][1] = $times;
            }

            if (!empty($rows)) {
                $placeholders = array();
                $values       = array();
                foreach ($rows as $bucket => $counts) {
                    $placeholders[] = '(%s, %s, %d, %d, %d)';
                    array_push($values, $property_id, $video_key, $bucket, $counts[0], $counts[1]);
                }
                // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- placeholders built above, all values passed through prepare.
                $wpdb->query($wpdb->prepare(
                    "INSERT INTO $engagement_table (property_id, video_key, bucket, reached_count, plays_count)
                     VALUES " . implode(', ', $placeholders) . "
                     ON DUPLICATE KEY UPDATE
                        reached_count = reached_count + VALUES(reached_count),
                        plays_count = plays_count + VALUES(plays_count)",
                    $values
                ));
            }
        }
    }

    wp_send_json_success();
}
add_action('wp_ajax_kantan_track_video', 'kantan_track_video');
add_action('wp_ajax_nopriv_kantan_track_video', 'kantan_track_video');

/* ───────────────────────── Dashboard queries ───────────────────────── */

/**
 * Headline totals for the Video tab over the last $days.
 */
function kantan_get_video_totals($days = 30) {
    global $wpdb;
    $table = $wpdb->prefix . 'kantan_video_events';

    $row = $wpdb->get_row($wpdb->prepare(
        "SELECT
            COUNT(DISTINCT video_key) AS videos,
            SUM(event_type = 'impression') AS impressions,
            SUM(event_type = 'play') AS plays,
            SUM(event_type = 'complete') AS completes,
            SUM(watch_seconds) AS watch_seconds
         FROM $table
         WHERE created_at >= DATE_SUB(NOW(), INTERVAL %d DAY)",
        $days
    ), ARRAY_A);

    $impressions = intval($row['impressions'] ?? 0);
    $plays       = intval($row['plays'] ?? 0);
    $completes   = intval($row['completes'] ?? 0);

    return array(
        'videos'          => intval($row['videos'] ?? 0),
        'impressions'     => $impressions,
        'plays'           => $plays,
        'completes'       => $completes,
        'watch_seconds'   => intval($row['watch_seconds'] ?? 0),
        'play_rate'       => $impressions > 0 ? round(($plays / $impressions) * 100, 1) : 0,
        'completion_rate' => $plays > 0 ? round(($completes / $plays) * 100, 1) : 0,
        'avg_watch'       => $plays > 0 ? intval(round(intval($row['watch_seconds'] ?? 0) / $plays)) : 0,
    );
}

/**
 * Per-video rows for the Video tab list, sorted by plays. Metrics come from
 * the events window; title/duration come from the registry.
 */
function kantan_get_video_rows($days = 30, $limit = 50) {
    global $wpdb;
    $events_table = $wpdb->prefix . 'kantan_video_events';
    $videos_table = $wpdb->prefix . 'kantan_videos';

    return $wpdb->get_results($wpdb->prepare(
        "SELECT
            e.video_key,
            e.provider,
            COALESCE(NULLIF(v.video_title, ''), e.video_key) AS video_title,
            v.duration,
            SUM(e.event_type = 'impression') AS impressions,
            SUM(e.event_type = 'play') AS plays,
            SUM(e.event_type = 'complete') AS completes,
            SUM(e.watch_seconds) AS watch_seconds,
            MAX(e.created_at) AS last_seen
         FROM $events_table e
         LEFT JOIN $videos_table v ON v.video_key = e.video_key
         WHERE e.created_at >= DATE_SUB(NOW(), INTERVAL %d DAY)
         GROUP BY e.video_key, e.provider, v.video_title, v.duration
         ORDER BY plays DESC, impressions DESC
         LIMIT %d",
        $days,
        $limit
    ));
}

/**
 * Everything the per-video detail modal needs, in one query pass.
 * Returns null when the video has no events in the window and no registry row.
 */
function kantan_get_video_detail($video_key, $days = 30) {
    global $wpdb;
    $events_table = $wpdb->prefix . 'kantan_video_events';
    $videos_table = $wpdb->prefix . 'kantan_videos';

    $registry = $wpdb->get_row($wpdb->prepare(
        "SELECT video_key, provider, video_title, duration, total_impressions, total_plays,
                total_completes, total_watch_seconds, first_seen, last_seen
         FROM $videos_table WHERE video_key = %s",
        $video_key
    ));

    $summary = $wpdb->get_row($wpdb->prepare(
        "SELECT
            SUM(event_type = 'impression') AS impressions,
            SUM(event_type = 'play') AS plays,
            SUM(event_type = 'play' AND autoplay = 1) AS autoplay_plays,
            SUM(event_type = 'complete') AS completes,
            SUM(watch_seconds) AS watch_seconds,
            COUNT(DISTINCT session_id) AS sessions
         FROM $events_table
         WHERE video_key = %s AND created_at >= DATE_SUB(NOW(), INTERVAL %d DAY)",
        $video_key,
        $days
    ), ARRAY_A);

    if (!$registry && intval($summary['impressions'] ?? 0) === 0) {
        return null;
    }

    $by_day = $wpdb->get_results($wpdb->prepare(
        "SELECT DATE(created_at) AS day,
                SUM(event_type = 'play') AS plays,
                SUM(watch_seconds) AS watch_seconds
         FROM $events_table
         WHERE video_key = %s AND created_at >= DATE_SUB(NOW(), INTERVAL %d DAY)
         GROUP BY DATE(created_at)
         ORDER BY day ASC",
        $video_key,
        $days
    ));

    $top_pages = $wpdb->get_results($wpdb->prepare(
        "SELECT page_url,
                SUM(event_type = 'play') AS plays,
                SUM(watch_seconds) AS watch_seconds
         FROM $events_table
         WHERE video_key = %s AND created_at >= DATE_SUB(NOW(), INTERVAL %d DAY)
           AND page_url <> ''
         GROUP BY page_url
         ORDER BY plays DESC, watch_seconds DESC
         LIMIT 10",
        $video_key,
        $days
    ));

    $impressions = intval($summary['impressions'] ?? 0);
    $plays       = intval($summary['plays'] ?? 0);
    $completes   = intval($summary['completes'] ?? 0);

    $detail = array(
        'video_key'   => $video_key,
        'provider'    => $registry ? $registry->provider : '',
        'video_title' => ($registry && $registry->video_title !== '') ? $registry->video_title : $video_key,
        'duration'    => $registry ? intval($registry->duration) : 0,
        'summary'     => array(
            'impressions'     => $impressions,
            'plays'           => $plays,
            'autoplay_plays'  => intval($summary['autoplay_plays'] ?? 0),
            'completes'       => $completes,
            'watch_seconds'   => intval($summary['watch_seconds'] ?? 0),
            'sessions'        => intval($summary['sessions'] ?? 0),
            'play_rate'       => $impressions > 0 ? round(($plays / $impressions) * 100, 1) : 0,
            'completion_rate' => $plays > 0 ? round(($completes / $plays) * 100, 1) : 0,
            'avg_watch'       => $plays > 0 ? intval(round(intval($summary['watch_seconds'] ?? 0) / $plays)) : 0,
        ),
        'by_day'      => array_map(function ($r) {
            return array(
                'day'           => $r->day,
                'plays'         => intval($r->plays),
                'watch_seconds' => intval($r->watch_seconds),
            );
        }, $by_day),
        'top_pages'   => array_map(function ($r) {
            return array(
                'page_url'      => $r->page_url,
                'plays'         => intval($r->plays),
                'watch_seconds' => intval($r->watch_seconds),
            );
        }, $top_pages),
        'all_time'    => $registry ? array(
            'impressions'   => intval($registry->total_impressions),
            'plays'         => intval($registry->total_plays),
            'completes'     => intval($registry->total_completes),
            'watch_seconds' => intval($registry->total_watch_seconds),
            'first_seen'    => $registry->first_seen,
            'last_seen'     => $registry->last_seen,
        ) : null,
        // Conditional enrichment (same pattern as conversion $ values): the
        // engagement curve is the paid layer, populated only when Kantan SEO
        // declares itself active + licensed via the filter below. The buckets
        // accrue regardless, so it lights up with full history the moment SEO
        // is licensed.
        'engagement'  => null,
    );

    if (kantan_video_paid_active()) {
        $engagement_table = $wpdb->prefix . 'kantan_video_engagement';
        $buckets = $wpdb->get_results($wpdb->prepare(
            "SELECT bucket, reached_count, plays_count FROM $engagement_table
             WHERE video_key = %s ORDER BY bucket ASC",
            $video_key
        ));
        if (!empty($buckets)) {
            $reached = array_fill(0, 100, 0);
            $replays = array_fill(0, 100, 0);
            foreach ($buckets as $b) {
                $idx = intval($b->bucket);
                if ($idx >= 0 && $idx <= 99) {
                    $reached[$idx] = intval($b->reached_count);
                    $replays[$idx] = intval($b->plays_count);
                }
            }
            $detail['engagement'] = array('reached' => $reached, 'plays' => $replays);
        }
    }

    // Companion plugins (Kantan SEO) attach their paid sections here —
    // e.g. conversion attribution.
    return apply_filters('kantan_video_detail', $detail, $video_key, $days);
}

/**
 * Whether the paid video layer (engagement curves, attribution) is unlocked.
 * Kantan SEO hooks this filter and returns its license state; without SEO
 * the filter default keeps it off.
 */
function kantan_video_paid_active() {
    return (bool) apply_filters('kantan_video_paid_active', false);
}

/* ───────────────────────── REST API ───────────────────────── */

function kantan_video_register_rest_routes() {
    $namespace = 'kantan/v1';

    register_rest_route($namespace, '/videos', array(
        'methods'             => 'GET',
        'callback'            => 'kantan_rest_get_videos',
        'permission_callback' => 'kantan_rest_permission',
    ));

    register_rest_route($namespace, '/videos/detail', array(
        'methods'             => 'GET',
        'callback'            => 'kantan_rest_get_video_detail',
        'permission_callback' => 'kantan_rest_permission',
    ));

    register_rest_route($namespace, '/video-settings', array(
        array(
            'methods'             => 'GET',
            'callback'            => 'kantan_rest_get_video_settings',
            'permission_callback' => 'kantan_rest_permission',
        ),
        array(
            'methods'             => 'PUT',
            'callback'            => 'kantan_rest_update_video_settings',
            'permission_callback' => 'kantan_rest_permission',
        ),
    ));
}
add_action('rest_api_init', 'kantan_video_register_rest_routes');

/**
 * GET /kantan/v1/videos?days=30
 * Video list + basic metrics (free tier).
 */
function kantan_rest_get_videos($request) {
    $days = max(1, min(3650, intval($request->get_param('days') ?: 30)));

    $rows = array_map(function ($row) {
        $impressions = intval($row->impressions);
        $plays       = intval($row->plays);
        $completes   = intval($row->completes);
        return array(
            'video_key'       => $row->video_key,
            'provider'        => $row->provider,
            'video_title'     => $row->video_title,
            'duration'        => intval($row->duration),
            'impressions'     => $impressions,
            'plays'           => $plays,
            'completes'       => $completes,
            'watch_seconds'   => intval($row->watch_seconds),
            'play_rate'       => $impressions > 0 ? round(($plays / $impressions) * 100, 1) : 0,
            'completion_rate' => $plays > 0 ? round(($completes / $plays) * 100, 1) : 0,
            'avg_watch'       => $plays > 0 ? intval(round(intval($row->watch_seconds) / $plays)) : 0,
            'last_seen'       => $row->last_seen,
        );
    }, kantan_get_video_rows($days));

    return rest_ensure_response(array(
        'days'   => $days,
        'totals' => kantan_get_video_totals($days),
        'videos' => $rows,
    ));
}

/**
 * GET /kantan/v1/videos/detail?key=&days=30
 * Per-video summary for the detail modal. Engagement buckets populate only
 * when Kantan SEO is active (conditional enrichment).
 */
function kantan_rest_get_video_detail($request) {
    $video_key = sanitize_text_field($request->get_param('key') ?: '');
    if ($video_key === '') {
        return new WP_Error('missing_key', 'Missing required parameter: key', array('status' => 400));
    }
    $days = max(1, min(3650, intval($request->get_param('days') ?: 30)));

    $detail = kantan_get_video_detail($video_key, $days);
    if ($detail === null) {
        return new WP_Error('not_found', 'No data for this video', array('status' => 404));
    }
    $detail['days'] = $days;
    return rest_ensure_response($detail);
}

/**
 * GET /kantan/v1/video-settings
 */
function kantan_rest_get_video_settings() {
    return rest_ensure_response(kantan_video_get_settings());
}

/**
 * PUT /kantan/v1/video-settings
 * Expects any subset of: { enabled, youtube, vimeo, html5, nocookie, exclude_autoplay }
 */
function kantan_rest_update_video_settings($request) {
    $body = $request->get_json_params();
    if (!is_array($body)) {
        return new WP_Error('invalid_data', 'Expected a JSON object of settings', array('status' => 400));
    }

    $settings = kantan_video_get_settings();
    foreach (array_keys($settings) as $key) {
        if (array_key_exists($key, $body)) {
            $settings[$key] = (bool) $body[$key];
        }
    }
    update_option('kantan_video_settings', $settings);

    return rest_ensure_response(array('updated' => true) + $settings);
}
