<?php

class Bug_Monitor_Log {

      public static $ignored_events = array();

      public static $record_types = array('functional/non_responsive_button', 'functional/form_submission', 'functional/form_abandonment', 'ux/non_responsive_element', 'ui/content_obstruction');

      public static $screenshot_types = array('ui/too_close_clickable', 'ui/layout', 'functional/broken_link');
      public static function get_logs($args){
            $args = wp_parse_args($args, array(
                  'status' => NULL,
                  'type' => NULL,
                  'level' => array('critical', 'error', 'warning'),
                  'event_hash' => NULL,
                  'page' => 1,
                  'limit' => 1001
            ));
            $log = array();

            $events_table = Bug_Monitor_Helper::$db->bug_monitor_events;
            $sessions_table = Bug_Monitor_Helper::$db->bug_monitor_sessions;
            $screenshots_table = Bug_Monitor_Helper::$db->bug_monitor_screenshots;
            $filter = self::build_where($args);
            if (Bug_Monitor::check_option('activated', 1) && !empty($args['event_hash'])){
                  self::maybe_decrypt($filter);
            }

            $limit = max(1, (int)$args['limit']);
            $from  = ((int)$args['page'] - 1) * $limit;
            $group_limit = " INNER JOIN (SELECT event_hash AS bm_eh FROM {$events_table} WHERE 1 = 1 {$filter} GROUP BY event_hash ORDER BY MAX(timestamp) DESC LIMIT " . esc_sql($from) . ", " . esc_sql($limit) . ") bmg ON bmg.bm_eh = {$events_table}.event_hash ";

            $events = Bug_Monitor_Helper::$db->get_results("SELECT {$sessions_table}.preview AS session_preview, {$sessions_table}.encrypted AS session_encrypted, {$screenshots_table}.source AS screenshot, {$screenshots_table}.preview AS screenshot_preview, {$screenshots_table}.encrypted AS screenshot_encrypted, {$events_table}.* FROM {$events_table} {$group_limit} LEFT JOIN {$sessions_table} ON {$sessions_table}.hash = {$events_table}.session_hash LEFT JOIN {$screenshots_table} ON {$screenshots_table}.hash = {$events_table}.screenshot_hash WHERE 1 = 1 {$filter} ORDER BY {$events_table}.timestamp DESC", ARRAY_A);

            foreach ($events as $event){
                  $key = $event['event_hash'];
                  $event['details'] = json_decode($event['details'], true);

                  if (!isset($log[$key])){
                        list($category, ) = explode('/', $event['type']);

                        $selector = false;
                        if(!empty($event['details']['selector'])){
                              $selector = $event['details']['selector'];
                        }
                        else if (!empty($event['details']['target'])){
                              $selector = $event['details']['target'];
                        }

                        $log[$key] = array(
                              'id' => $event['ID'],
                              'event_hash' => $event['event_hash'],
                              'category' => $category,
                              'type' => $event['type'],
                              'level' => $event['level'],
                              'details' => $event['details'],
                              'selector' => $selector,
                              'count' => 1,
                              'urls' => array(),
                              'browsers' => array(),
                              'timestamps' => array(),
                              'visuals' => array(),
                              'visual_type' => NULL,
                              'preview_link' => '',
                              'encrypted' => $event['encrypted'],
                              'status' => $event['status'],
                        );
                  }
                  else {
                        $log[$key]['count']++;
                  }
                  if ($event['encrypted'] == 1){
                        $log[$key]['encrypted'] = 1;
                  }

                  if ($event['session_encrypted'] == 1 || $event['screenshot_encrypted'] == 1){
                        $log[$key]['visual_encrypted'] = 1;
                  }
                  if (isset($event['details']['duration'])){
                        if (!isset($log[$key]['duration'])){
                              $log[$key]['duration'] = array('min' => PHP_INT_MAX, 'max' => 0);
                        }

                        $log[$key]['duration']['min'] = min($log[$key]['duration']['min'], $event['details']['duration']);
                        $log[$key]['duration']['max'] = max($log[$key]['duration']['max'], $event['details']['duration']);
                  }

                  if (!empty($event['URL'])){
                        if ($event['type'] == 'functional/broken_link' && $log[$key]['encrypted'] != 1 && !empty($event['details']['url'])){
                              $event['URL'] = $event['details']['url'];
                        }

                        $log[$key]['urls'][$event['URL']] = (!empty($log[$key]['urls'][$event['URL']]) ? $log[$key]['urls'][$event['URL']] + 1 : 1);

                        if (empty($log[$key]['preview_link']) && (!empty($event['details']['selector']) || !empty($event['details']['target']))){
                              $selector = (!empty($event['details']['selector']) ? $event['details']['selector'] : $event['details']['target']);
                              if (!empty($selector)){
                                    $log[$key]['preview_link'] = esc_url(add_query_arg('bm-preview', urlencode($selector), $event['URL']));
                              }
                        }
                  }

                  if (!empty($event['ua'])){
                        $log[$key]['browsers'][$event['ua']] = (!empty($log[$key]['browsers'][$event['ua']]) ? $log[$key]['browsers'][$event['ua']] + 1 : 1);
                  }
                  $log[$key]['timestamps'][] = $event['timestamp'];
                  if (!empty($event['session_hash'])){
                        $log[$key]['visuals'][] = array(
                              'type' => 'record',
                              'hash' => $event['session_hash'],
                              'preview' => Bug_Monitor_Helper::ugz($event['session_preview']),
                        );
                        $log[$key]['visual_type'] = 'video';
                  }
                  else if (!empty($event['screenshot'])){
                        $log[$key]['visuals'][] = array(
                              'type' => 'image',
                              'screenshot' => Bug_Monitor_Helper::ugz($event['screenshot']),
                              'preview' => Bug_Monitor_Helper::ugz($event['screenshot_preview'])
                        );
                        $log[$key]['visual_type'] = 'image';
                  }
                  else if (isset($event['details']['preview_src'])){
                        $log[$key]['visuals'][0] = array(
                              'type' => 'image',
                              'screenshot' => $event['details']['preview_src'],
                              'preview' => $event['details']['preview_src'],
                              'adjust_bgcolor' => true
                        );
                  }
                  else if ($event['type'] == 'php/email_sending_error'){
                        $log[$key]['visuals'][0] = array(
                              'type' => 'email',
                              'subject' => $event['details']['subject'],
                              'to' => $event['details']['to'],
                              'message' => $event['details']['message']
                        );
                        $log[$key]['visual_type'] = 'email';
                  }

            }

            return $log;
      }
      private static function build_where($args){
            $args = wp_parse_args($args, array(
                  'status' => NULL,
                  'type' => NULL,
                  'level' => array('critical', 'error', 'warning'),
                  'event_hash' => NULL,
            ));

            $events_table = Bug_Monitor_Helper::$db->bug_monitor_events;

            $where = '';
            if (!empty($args['status'])){
                  $where .= ' AND status IN (' . implode(',', array_map('intval', (array)$args['status'])) . ')';
            }
            if (!empty($args['type'])){
                  $where .= ' AND type IN (' . implode(',', array_map(function($e){
                        return "'" . esc_sql($e) . "'";
                  },(array)$args['type'])) . ')';
            }
            if (!empty($args['level'])){
                  $where .= ' AND level IN (' . implode(',', array_map(function($e){
                        return "'" . esc_sql($e) . "'";
                  },(array)$args['level'])) . ')';
            }
            if (!empty($args['event_hash'])){
                  $where .= ' AND event_hash = "' . esc_sql($args['event_hash']) . '"';
            }
            $where .= ' AND ' . $events_table . '.timestamp <= "' . gmdate('Y-m-d H:i:s') . '"';

            return $where;
      }

      public static function get_log_count($args){
            $count = get_transient('bug_monitor_log_count');
            if (false === $count){
                  $events_table = Bug_Monitor_Helper::$db->bug_monitor_events;
                  $where = self::build_where($args);
                  $count = (int)Bug_Monitor_Helper::$db->get_var("SELECT COUNT(DISTINCT event_hash) FROM {$events_table} WHERE 1 = 1 {$where}");
                  set_transient('bug_monitor_log_count', $count, 300);
            }
            return $count;
      }


      public static function get_counts(){
            $counts = get_transient('bug_monitor_counts');
            if (false !== $counts){
                  return $counts;
            }

            $events_table = Bug_Monitor_Helper::$db->bug_monitor_events;
            $now = gmdate('Y-m-d H:i:s');
            $level_sql = "level IN ('critical', 'error', 'warning')";

            $collect = function($status_sql) use ($events_table, $now, $level_sql) {
                  $data = array('category' => array(), 'level' => array(), 'total' => 0);

                  $cats = Bug_Monitor_Helper::$db->get_results("SELECT SUBSTRING_INDEX(type, '/', 1) AS k, COUNT(DISTINCT event_hash) AS c FROM {$events_table} WHERE {$status_sql} AND {$level_sql} AND timestamp <= '{$now}' GROUP BY k", ARRAY_A);
                  foreach ((array)$cats as $row){
                        $data['category'][$row['k']] = (int)$row['c'];
                  }

                  $levels = Bug_Monitor_Helper::$db->get_results("SELECT level AS k, COUNT(DISTINCT event_hash) AS c FROM {$events_table} WHERE {$status_sql} AND {$level_sql} AND timestamp <= '{$now}' GROUP BY k", ARRAY_A);
                  foreach ((array)$levels as $row){
                        $data['level'][$row['k']] = (int)$row['c'];
                  }

                  $data['total'] = (int)Bug_Monitor_Helper::$db->get_var("SELECT COUNT(DISTINCT event_hash) FROM {$events_table} WHERE {$status_sql} AND {$level_sql} AND timestamp <= '{$now}'");

                  return $data;
            };

            $counts = array(
                  'active'  => $collect('status IN (1, 2)'),
                  'ignored' => $collect('status = -1'),
            );

            set_transient('bug_monitor_counts', $counts, 300);

            return $counts;
      }
      public static function handle_rest_request($request) {
            $action = $request['action'];
            $_POST = stripslashes_deep($_POST);

            switch ($action) {
                  case 'event':
                  self::record_client_event();
                  break;
                  case 'session':
                  return self::save_session_dom();
                  break;
                  case 'mutations':
                  self::save_mutations();
                  break;
                  case 'screenshot':
                  return self::save_screenshot();
                  break;
                  case 'single_event':
                  self::record_single_event();
                  break;
            }
      }
      public static function save_session_dom(){
            $time = time();
            $timestamp = gmdate('Y-m-d H:i:s', $time);
            $tick = intval($time / apply_filters('bug_monitor/session_tick', 43200));
            $session_hash = md5($_POST['type'] . $_POST['details'] . $_POST['url'] . $_POST['device'] . $tick);
            $source = Bug_Monitor_Helper::gz(Bug_Monitor_Helper::decode($_POST['dom']));
            $preview = Bug_Monitor_Helper::gz($_POST['preview']);
            $resolution = $_POST['resolution'];
            $mutations = Bug_Monitor_Helper::gz(substr(json_encode(json_decode(Bug_Monitor_Helper::decode($_POST['mutations']))),1,-1));
            $ua = (!empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');

            Bug_Monitor_Helper::$db->query(Bug_Monitor_Helper::$db->prepare("INSERT IGNORE INTO " . Bug_Monitor_Helper::$db->bug_monitor_sessions . " (hash, ua, resolution, source, preview, mutations, timestamp, encrypted) VALUES (%s, %s, %s, %s, %s, %s, %s, 1)", $session_hash, $ua, $resolution, $source, $preview, $mutations, $timestamp));

            if (Bug_Monitor_Helper::$db->rows_affected > 0){
                  return new WP_REST_Response(array('result' => true, 'hash' => $session_hash), 200);
            }
            else {
                  return new WP_REST_Response(array('result' => false), 200);
            }

            die;
      }
      public static function save_mutations(){
            $session_hash = $_POST['session_hash'];
            $mutations = Bug_Monitor_Helper::gz(substr(json_encode(json_decode(Bug_Monitor_Helper::decode($_POST['mutations']))), 1, -1));

            Bug_Monitor_Helper::$db->query(Bug_Monitor_Helper::$db->prepare("UPDATE " . Bug_Monitor_Helper::$db->bug_monitor_sessions . " SET mutations = CONCAT(mutations, ',', %s) WHERE hash = %s", $mutations, $session_hash));
            die;
      }
      public static function save_screenshot(){
            $time = time();
            $tick = intval($time / apply_filters('bug_monitor/rate_limit_screenshot_tick', 43200));
            $source = Bug_Monitor_Helper::gz($_POST['source']);
            $preview = Bug_Monitor_Helper::gz($_POST['preview']);
            $hash = md5($_POST['details'] . $_POST['url'] . $_POST['resolution'] . $_POST['device'] . $tick);

            Bug_Monitor_Helper::$db->query(Bug_Monitor_Helper::$db->prepare("INSERT IGNORE INTO " . Bug_Monitor_Helper::$db->bug_monitor_screenshots . " (hash, source, preview, encrypted) VALUES (%s, %s, %s, 1)", $hash, $source, $preview));

            if (Bug_Monitor_Helper::$db->rows_affected > 0){
                  return new WP_REST_Response(array('result' => true, 'hash' => $hash), 200);
            }
            else {
                  return new WP_REST_Response(array('result' => false, 'hash' => $hash), 200);
            }

            die;
      }
      public static function record_client_event(){
            $json = file_get_contents('php://input');
            $data = json_decode(Bug_Monitor_Helper::decode($json), true);
            if (empty($data)){
                  return;
            }

            $has_ignored = $has_ignored_screenshot = false;
            $wfe = time() / 604800;
            $url = $url_to_save = $data['url'];
            $session_hash = (!empty($data['session_hash']) ? $data['session_hash'] : '');
            $max_packet_size = get_transient('bug_monitor_max_packet_size');
            $time = time();
            $ua = (!empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');
            if (empty($max_packet_size)){
                  $max_packet_size = Bug_Monitor_Helper::$db->get_var("SELECT @@max_allowed_packet;");
                  $max_packet_size = max(16384, $max_packet_size - 100);
                  set_transient('bug_monitor_max_packet_size', $max_packet_size, 3600);
            }
            if (empty($url)){
                  return;
            }
            $bounce = ($data['bounce'] ? 1 : 0);
            Bug_Monitor_Helper::$db->query(Bug_Monitor_Helper::$db->prepare("
                  INSERT INTO `" . Bug_Monitor_Helper::$db->bug_monitor_hits . "` (`URL`, `wfe`, `hit`, bounce)
                  VALUES (%s, %d, 1, %d)
                  ON DUPLICATE KEY UPDATE
                  `hit` = CASE
                  WHEN `wfe` = VALUES(wfe) THEN `hit` + 1
                  ELSE 1
                  END,
                  `bounce` = CASE
                  WHEN `wfe` = VALUES(wfe) THEN `bounce` + VALUES(bounce)
                  ELSE 0
                  END,
                  `wfe` = VALUES(wfe)
                  ", $url, $wfe, $bounce)
            );

            $values = array();
            $packet_size = 0;
            $timestamp = gmdate('Y-m-d H:i:s');
            if (!empty($session_hash) && !empty($data['mutations'])){
                  $mutations = Bug_Monitor_Helper::gz(substr(json_encode($data['mutations']), 1, -1));
                  Bug_Monitor_Helper::$db->query(Bug_Monitor_Helper::$db->prepare("UPDATE " . Bug_Monitor_Helper::$db->bug_monitor_sessions . " SET mutations = CONCAT(mutations, ',',  %s) WHERE hash = %s", $mutations, $session_hash));
            }
            if (!empty($data['logs'])){
                  foreach ($data['logs'] as $row){
                        $details = json_encode($row['details']);
                        $event_id = self::get_event_hash($row, $url, time());
                        $event_hash = self::get_event_hash($row, $url);

                        $screenshot = (!empty($row['screenshot']) ? $row['screenshot'] : '');

                        $encrypted = (!empty($row['encrypted']) ? 1 : 0);
                        if (self::is_ignored($event_hash, $row['type'])){
                              $has_ignored = true;

                              if (!empty($row['screenshot'])){
                                    $has_ignored_screenshot = true;
                              }

                              continue;
                        }
                        $_session_hash = (in_array($row['type'], self::$record_types) ? $session_hash : '');
                        $value = '("' . $event_id . '", "'. $event_hash . '", "' . esc_sql($_session_hash) . '", "' .
                        esc_sql($screenshot) . '", "' . esc_sql($row['level']) . '","' . esc_sql($row['type']) . '","' . esc_url($url_to_save) . '","' . esc_sql($details) . '","' . esc_sql($ua) . '","' . $timestamp . '","1","' . $encrypted . '")';
                        $url_to_save = $url;
                        if ($packet_size + strlen($value) < $max_packet_size){
                              $values[] = $value;
                              $packet_size += strlen($value);
                        }
                        else {
                              if (!empty($values)){
                                    Bug_Monitor_Helper::$db->query("INSERT IGNORE INTO " . Bug_Monitor_Helper::$db->bug_monitor_events . " (id, event_hash, session_hash, screenshot_hash, level, type, url, details, ua, timestamp, status, encrypted) VALUES " . implode(',', $values) . ";");
                              }
                              $values = array($value);
                              $packet_size = strlen($value);
                        }
                  }
                  if (!empty($values)){
                        Bug_Monitor_Helper::$db->query("INSERT IGNORE INTO " . Bug_Monitor_Helper::$db->bug_monitor_events . " (id, event_hash, session_hash, screenshot_hash, level, type, url, details, ua, timestamp, status, encrypted) VALUES " . implode(',', $values) . ";");
                  }

                  if (Bug_Monitor_Helper::$db->rows_affected > 0){
                        delete_transient('bug_monitor_log_count');
            delete_transient('bug_monitor_counts');
                  }
            }
            if ($has_ignored && ($has_ignored_screenshot || !empty($session_hash))){
                  self::clear_abandoned_visuals();
            }

            die;
      }
      public static function record_single_event(){
            $time = time();
            $timestamp = gmdate('Y-m-d H:i:s', $time);
            $tick = intval($time / apply_filters('bug_monitor/session_tick', 43200));
            $session_hash = md5($_POST['type'] . $_POST['details'] . $_POST['url'] . $_POST['device'] . $tick);
            $source = Bug_Monitor_Helper::gz(Bug_Monitor_Helper::decode($_POST['dom']));
            $preview = Bug_Monitor_Helper::gz($_POST['preview']);
            $resolution = $_POST['resolution'];
            $mutations = Bug_Monitor_Helper::gz(substr(json_encode(json_decode(Bug_Monitor_Helper::decode($_POST['mutations']))),1,-1));
            $ua = (!empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');

            $row = array(
                  'type' => $_POST['type'],
                  'details' => json_decode($_POST['details'], true)
            );
            $event_id = self::get_event_hash($row, $_POST['url'], time());
            $event_hash = self::get_event_hash($row, $_POST['url']);

            $encrypted = (!empty($_POST['encrypted']) ? 1 : 0);
            if (self::is_ignored($event_hash, $row['type'])){
                  die;
            }

            Bug_Monitor_Helper::$db->query(Bug_Monitor_Helper::$db->prepare("INSERT IGNORE INTO " . Bug_Monitor_Helper::$db->bug_monitor_events . " (id, event_hash, session_hash, level, type, url, details, timestamp, status, encrypted) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, 1, %s);", $event_id, $event_hash, $session_hash, $_POST['level'], $_POST['type'], $_POST['url'], $_POST['details'], $timestamp, $encrypted));

            if (Bug_Monitor_Helper::$db->rows_affected > 0){
                  Bug_Monitor_Helper::$db->query(Bug_Monitor_Helper::$db->prepare("INSERT IGNORE INTO " . Bug_Monitor_Helper::$db->bug_monitor_sessions . " (hash, ua, resolution, source, preview, mutations, timestamp, encrypted) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)", $session_hash, $ua, $resolution, $source, $preview, $mutations, $timestamp, $encrypted));
                  delete_transient('bug_monitor_log_count');
            delete_transient('bug_monitor_counts');
            }

            die;
      }
      public static function get_event_hash($row, $url, $time = 0){
            list($category, ) = explode('/', $row['type']);

            if (is_array($row['details'])){
                  $details = array();
                  foreach ($row['details'] as $key => $value){
                        if (in_array($key, array('duration', 'value'))){
                              continue;
                        }

                        if ($key == 'cookies'){
                              continue;
                        }
                        if ($category == 'js' && $key == 'filename' && $value == $url){
                              continue;
                        }

                        $details[$key] = $value;
                  }
                  $details = json_encode($details);
            }
            else {
                  $details = $row['details'];
            }

            if (in_array($category, array('seo', 'ui')) || $row['type'] == 'ux/bounce'){
                  $base = $row['type'] . $details . $url;
                  $tick = intval($time / apply_filters('bug_monitor/event_tick', 43200, $row['type'], $details, $url));
            }
            else if (in_array($category, array('webvitals'))){
                  $base = $row['type'] . $details . $url;
                  $tick = intval($time / apply_filters('bug_monitor/event_tick', 300, $row['type'], $details, $url));
            }
            else if (in_array($row['type'], array('functional/form_abandonment'))){
                  $base = $row['type'] . $details . $url;
                  $tick = intval($time / apply_filters('bug_monitor/event_tick', 30, $row['type'], $details, $url));
            }
            else {
                  $base = $row['type'] . $details;
                  $tick = intval($time / apply_filters('bug_monitor/event_tick', 60, $row['type'], $details, $url));
            }

            return apply_filters('bug_monitor/event_id', md5($base . $tick), $row['type'], $details, $url, $tick);
      }
      public static function is_ignored($event_hash, $event_type){
            if (empty(self::$ignored_events)){
                  self::$ignored_events = Bug_Monitor_Helper::$db->get_col("SELECT event_hash FROM " . Bug_Monitor_Helper::$db->bug_monitor_events . " WHERE status = -1");
            }
            if (in_array($event_hash, self::$ignored_events)){
                  return true;
            }
            if (Bug_Monitor::check_option($event_type, 'on', '!=')){
                  return true;
            }

            return false;
      }

      public static function solve($event_hash){
            Bug_Monitor_Helper::$db->delete(Bug_Monitor_Helper::$db->bug_monitor_events, array('event_hash' => $event_hash));
            self::clear_abandoned_visuals();
            delete_transient('bug_monitor_log_count');
            delete_transient('bug_monitor_counts');
      }
      public static function ignore($by){
            if (!empty($by['event_hash'])){
                  Bug_Monitor_Helper::$db->query(Bug_Monitor_Helper::$db->prepare("UPDATE " . Bug_Monitor_Helper::$db->bug_monitor_events . " SET status = -1 WHERE event_hash = %s ORDER BY session_hash, screenshot_hash LIMIT 1", $by['event_hash']));
                  Bug_Monitor_Helper::$db->query(Bug_Monitor_Helper::$db->prepare("DELETE FROM " . Bug_Monitor_Helper::$db->bug_monitor_events . " WHERE event_hash = %s AND status > 0", $by['event_hash']));
            }
            else if (!empty($by['category'])){
                  foreach (Bug_Monitor_Config::$settings as $key => $value){
                        if (preg_match('~^' . $by['category'] . '/~', $key)){
                              Bug_Monitor::update_option($key, '');
                        }
                  }

                  Bug_Monitor_Helper::$db->query(Bug_Monitor_Helper::$db->prepare("DELETE FROM " . Bug_Monitor_Helper::$db->bug_monitor_events . " WHERE type LIKE %s", $by['category'] . '/%'));
            }
            else if (!empty($by['type'])){
                  Bug_Monitor::update_option($by['type'], '');
                  Bug_Monitor_Helper::$db->delete(Bug_Monitor_Helper::$db->bug_monitor_events, array('type' => $by['type']));
            }

            self::clear_abandoned_visuals();
            delete_transient('bug_monitor_log_count');
            delete_transient('bug_monitor_counts');
      }

      public static function undo_ignore($by){
            if (!empty($by['event_hash'])){
                  Bug_Monitor_Helper::$db->query(Bug_Monitor_Helper::$db->prepare("UPDATE " . Bug_Monitor_Helper::$db->bug_monitor_events . " SET status = 2 WHERE event_hash = %s ORDER BY session_hash, screenshot_hash LIMIT 1", $by['event_hash']));
            }
            delete_transient('bug_monitor_log_count');
            delete_transient('bug_monitor_counts');
      }

      public static function reset(){
            Bug_Monitor_Helper::$db->query("TRUNCATE TABLE " . Bug_Monitor_Helper::$db->bug_monitor_events);
            Bug_Monitor_Helper::$db->query("TRUNCATE TABLE " . Bug_Monitor_Helper::$db->bug_monitor_screenshots);
            Bug_Monitor_Helper::$db->query("TRUNCATE TABLE " . Bug_Monitor_Helper::$db->bug_monitor_sessions);
            delete_transient('bug_monitor_log_count');
            delete_transient('bug_monitor_counts');
      }
      public static function check_bounce_rate(){
            if (date('N') < 3 || Bug_Monitor::check_option('ux/bounce_rate', 'on', '!=')){
                  return;
            }

            $time = time();
            $timestamp = gmdate('Y-m-d H:i:s', $time);
            foreach (Bug_Monitor_Helper::$db->get_results("SELECT *, (bounce * 1.0 / hit) AS bounce_rate FROM " . Bug_Monitor_Helper::$db->bug_monitor_hits . " WHERE hit > 10 AND (bounce / hit) > 0.5") as $row){
                  $event_row = array('type' => 'ux/bounce', 'level' => 'warning',  'details' => '{"rate": ' . $row->bounce_rate . '}');
                  $event_id = self::get_event_hash($event_row, $row->URL, $time);
                  $event_hash = self::get_event_hash($event_row, $row->URL);

                  Bug_Monitor_Helper::$db->query(Bug_Monitor_Helper::$db->prepare("INSERT IGNORE INTO " . Bug_Monitor_Helper::$db->bug_monitor_events . " (id, event_hash, level, type, url, details, timestamp, status) VALUES (%s, %s, %s, %s, %s, %s, %s, 1);", $event_id, $event_hash, $event_row['level'], $event_row['type'], $row->URL, $event_row['details'], $timestamp));
            }

      }

      public static function delete_expired_events(){
            $retention_days = (int)Bug_Monitor::get_option('system/event_retention');

            if (!empty($retention_days)){
                  $events_table = Bug_Monitor_Helper::$db->bug_monitor_events;
                  $timestamp = gmdate('Y-m-d H:i:s', time() - DAY_IN_SECONDS * $retention_days);
                  Bug_Monitor_Helper::$db->query("DELETE FROM {$events_table} WHERE timestamp < '{$timestamp}'");
                  delete_transient('bug_monitor_log_count');
            delete_transient('bug_monitor_counts');
            }
      }

      public static function clear_abandoned_visuals(){
            $session_table = Bug_Monitor_Helper::$db->bug_monitor_sessions;
            $screenshot_table = Bug_Monitor_Helper::$db->bug_monitor_screenshots;
            $events_table = Bug_Monitor_Helper::$db->bug_monitor_events;
            $timestamp = gmdate('Y-m-d H:i:s', time() - 600);

            // Delete sessions that have no events and are older than 10 minutes
            Bug_Monitor_Helper::$db->query("DELETE {$session_table} FROM {$session_table} LEFT JOIN {$events_table} ON {$session_table}.hash = {$events_table}.session_hash WHERE {$events_table}.session_hash IS NULL AND {$session_table}.timestamp < '{$timestamp}'");

            // Delete screenshots that have no events and are older than 10 minutes
            Bug_Monitor_Helper::$db->query("DELETE {$screenshot_table} FROM {$screenshot_table} LEFT JOIN {$events_table} ON {$screenshot_table}.hash = {$events_table}.screenshot_hash WHERE {$events_table}.screenshot_hash IS NULL AND {$screenshot_table}.timestamp < '{$timestamp}'");
      }
      public static function maybe_decrypt($where){
            $session_table = Bug_Monitor_Helper::$db->bug_monitor_sessions;
            $screenshot_table = Bug_Monitor_Helper::$db->bug_monitor_screenshots;
            $events = Bug_Monitor_Helper::$db->get_results("SELECT ID, session_hash, screenshot_hash, details, encrypted FROM " .  Bug_Monitor_Helper::$db->bug_monitor_events . " WHERE 1 = 1 {$where}", ARRAY_A);
            if (count($events) > 0){
                  $details = array();
                  $session_hashes = array();
                  $screenshot_hashes = array();

                  foreach ($events as $event){
                        if ($event['encrypted'] == 1){
                              $details[$event['ID']] = $event['details'];
                        }
                        $session_hashes[] = $event['session_hash'];
                        $screenshot_hashes[] = $event['screenshot_hash'];
                  }

                  if (!empty($details)){
                        $res = Bug_Monitor_API::request('decrypt', $details);

                        if (!empty($res)){
                              $results = json_decode($res['body'], true);
                              if (empty($results) || empty($results['success'])){
                                    Bug_Monitor::update_option('activated', -1);
                                    Bug_Monitor::update_option('api-error', $res['body']);
                              }
                              else {
                                    Bug_Monitor::update_option('api-error', false);

                                    $values = array();
                                    foreach ($results['decrypted'] as $id => $value) {
                                        $values[] = Bug_Monitor_Helper::$db->prepare('(%s, %s)', $id, json_encode($value));
                                    }
                                    $valuesString = implode(", ", $values);

                                    $query = "INSERT INTO " . Bug_Monitor_Helper::$db->bug_monitor_events . " (ID, details) VALUES $valuesString ON DUPLICATE KEY UPDATE details = VALUES(details), encrypted = 0;";

                                    Bug_Monitor_Helper::$db->query($query);
                              }
                        }
                  }
            }
            if (!empty($session_hashes)){
                  $session_placeholders = implode(',', array_fill(0, count($session_hashes), '%s'));
                  $sessions = Bug_Monitor_Helper::$db->get_results(Bug_Monitor_Helper::$db->prepare("SELECT hash, mutations FROM " .  Bug_Monitor_Helper::$db->bug_monitor_sessions . " WHERE encrypted = 1 AND hash IN ({$session_placeholders}) LIMIT 100", $session_hashes), OBJECT_K);
                  if (count($sessions) > 0){
                        $sessions_decoded = array_map(function($obj) {
                              $mutation_list = explode(',', (string)$obj->mutations);
                              $mutations = array();
                              foreach ($mutation_list as $mutation_list_element) {
                                    $mutation = Bug_Monitor_Helper::ugz($mutation_list_element);
                                    if (!empty($mutation) && $mutation != 'ul'){
                                          $mutations[] = $mutation;
                                    }
                              }

                              return json_decode('{' . implode(',', $mutations) . '}', true);
                        }, $sessions);

                        $sessions_array_keys = array_map(function($session) {
                              return json_encode(array_keys($session));
                        }, $sessions_decoded);


                        $res = Bug_Monitor_API::request('decrypt', $sessions_array_keys);

                        if (!empty($res)){
                              $results = json_decode($res['body'], true);
                              if (empty($results) || empty($results['success'])){
                                    Bug_Monitor::update_option('activated', -1);
                                    Bug_Monitor::update_option('api-error', $res['body']);
                              }
                              else {
                                    Bug_Monitor::update_option('api-error', false);

                                    if (!empty($results['decrypted'])){
                                          $sessions_decrypted = array();
                                          foreach ((array)$results['decrypted'] as $key => $value) {
                                                foreach ((array)$value as $ets => $dts){
                                                      $sessions_decrypted[$key][$dts] = $sessions_decoded[$key][$ets];
                                                }
                                          }

                                          $sessions_decrypted = array_map(function($element) {
                                                return Bug_Monitor_Helper::gz(json_encode($element));
                                          }, $sessions_decrypted);
                                    }


                                    foreach ($sessions_decrypted as $hash => $value) {
                                          Bug_Monitor_Helper::$db->update(Bug_Monitor_Helper::$db->bug_monitor_sessions, array('mutations' => $value, 'encrypted' => 0), array('hash' => $hash));
                                    }

                              }
                        }
                  }
            }
            if (!empty($screenshot_hashes)){
                  $screenshot_placeholders = implode(',', array_fill(0, count($screenshot_hashes), '%s'));
                  $screenshots = Bug_Monitor_Helper::$db->get_results(Bug_Monitor_Helper::$db->prepare("SELECT hash, source FROM " .  Bug_Monitor_Helper::$db->bug_monitor_screenshots . " WHERE encrypted = 1 AND hash IN ({$screenshot_placeholders}) LIMIT 100", $screenshot_hashes), ARRAY_A);
                  if (count($screenshots) > 0){
                        $screenshot_secret = $screenshot_plain = array();
                        foreach ($screenshots as $screenshot){
                              $decoded = Bug_Monitor_Helper::ugz($screenshot['source']);
                              list($secret, $plain) = explode(',', (string)$decoded);

                              $screenshot_secret[$screenshot['hash']] = json_encode(array($secret));
                              $screenshot_plain[$screenshot['hash']] = $plain;
                        }

                        $res = Bug_Monitor_API::request('decrypt', $screenshot_secret);

                        if (!empty($res)){
                              $results = json_decode($res['body'], true);
                              if (empty($results) || empty($results['success'])){
                                    //Bug_Monitor::update_option('activated', -1);
                                    //Bug_Monitor::update_option('api-error', $res['body']);
                              }
                              else {
                                    Bug_Monitor::update_option('api-error', false);

                                    if (!empty($results['decrypted'])){
                                          $screenshots_decrypted = array();
                                          foreach ((array)$results['decrypted'] as $key => $value) {
                                                foreach ((array)$value as $ets => $dts){
                                                      $screenshots_decrypted[$key] = Bug_Monitor_Helper::gz(array_pop($value) . $screenshot_plain[$key]);
                                                }
                                          }
                                    }

                                    foreach ($screenshots_decrypted as $hash => $value) {
                                          Bug_Monitor_Helper::$db->update(Bug_Monitor_Helper::$db->bug_monitor_screenshots, array('source' => $value, 'encrypted' => 0), array('hash' => $hash));
                                    }
                              }
                        }
                  }
            }
      }

}