<?php
/**
 * Campaign, event, and analytics persistence.
 *
 * @package SwairishXHeaderBarNotificationOffer
 */

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

class SwairishX_Storage {
    const POST_TYPE = 'swairishx_campaign';
    const META_KEY = '_swairishx_config';

    public static function register_post_type() {
        register_post_type(
            self::POST_TYPE,
            array(
                'labels' => array(
                    'name' => __('Campaigns', 'swairish-header-bar-offer-notifications'),
                    'singular_name' => __('Campaign', 'swairish-header-bar-offer-notifications'),
                ),
                'public' => false,
                'show_ui' => false,
                'show_in_rest' => false,
                'supports' => array('title', 'author', 'revisions'),
                'capability_type' => 'post',
                'map_meta_cap' => true,
            )
        );
    }

    public static function install_tables() {
        global $wpdb;
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        $charset = $wpdb->get_charset_collate();
        $events = $wpdb->prefix . 'swairishx_events';
        $stats = $wpdb->prefix . 'swairishx_stats';

        dbDelta("CREATE TABLE {$events} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            event_key varchar(191) NOT NULL,
            source varchar(60) NOT NULL,
            event_type varchar(60) NOT NULL,
            occurred_at datetime NOT NULL,
            payload longtext NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY event_key (event_key),
            KEY source_type (source,event_type),
            KEY occurred_at (occurred_at)
        ) {$charset};");

        dbDelta("CREATE TABLE {$stats} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            campaign_id bigint(20) unsigned NOT NULL,
            stat_date date NOT NULL,
            impressions bigint(20) unsigned NOT NULL DEFAULT 0,
            clicks bigint(20) unsigned NOT NULL DEFAULT 0,
            dismissals bigint(20) unsigned NOT NULL DEFAULT 0,
            PRIMARY KEY  (id),
            UNIQUE KEY campaign_date (campaign_id,stat_date),
            KEY stat_date (stat_date)
        ) {$charset};");
    }

    public function save_campaign(array $draft, $id = 0) {
        $draft = $this->sanitize_campaign($draft);
        $post = array(
            'ID' => absint($id),
            'post_type' => self::POST_TYPE,
            'post_title' => $draft['name'],
            'post_status' => $draft['status'],
        );
        $campaign_id = $id ? wp_update_post($post, true) : wp_insert_post($post, true);
        if (is_wp_error($campaign_id)) {
            return $campaign_id;
        }
        update_post_meta($campaign_id, self::META_KEY, $draft);
        return $campaign_id;
    }

    public function sanitize_campaign(array $input) {
        $defaults = array(
            'schema_version' => 1,
            'name' => __('Untitled notification', 'swairish-header-bar-offer-notifications'),
            'status' => 'draft',
            'type' => 'notification_bar',
            'integration' => 'manual',
            'source_identifier' => '',
            'headline' => '',
            'message' => '',
            'cta_label' => '',
            'cta_url' => '',
            'coupon_code' => '',
            'coupon_copied_text' => __('Copied', 'swairish-header-bar-offer-notifications'),
            'show_countdown' => 0,
            'image_url' => '',
            'image_alt' => '',
            'image_position' => 'start',
            'position' => 'top',
            'theme' => 'signal',
            'background' => '#10261f',
            'foreground' => '#f5fbf7',
            'accent' => '#6ee7b7',
            'sticky' => 1,
            'close_button' => 1,
            'dismiss_hours' => 24,
            'reappearance' => 'after_hours',
            'delay_ms' => 0,
            'auto_hide_ms' => 0,
            'start_at' => '',
            'end_at' => '',
            'include_paths' => '',
            'exclude_paths' => '',
            'utm_source' => 'swairishx',
            'utm_medium' => 'notification',
            'tracking' => 1,
        );
        $extension_input = $input;
        $input = array_intersect_key($input, $defaults);
        $data = wp_parse_args($input, $defaults);
        $allowed_types = array_keys(swairishx()->registry()->all('notification'));
        $allowed_integrations = array_keys(swairishx()->registry()->all('integration'));
        $data['schema_version'] = 1;
        $data['name'] = sanitize_text_field($data['name']);
        $data['status'] = in_array($data['status'], array('draft', 'publish', 'pending'), true) ? $data['status'] : 'draft';
        $data['type'] = in_array($data['type'], $allowed_types, true) ? $data['type'] : 'notification_bar';
        $data['integration'] = in_array($data['integration'], $allowed_integrations, true) ? $data['integration'] : 'manual';
        foreach (array('headline', 'cta_label', 'coupon_code', 'coupon_copied_text', 'position', 'theme', 'utm_source', 'utm_medium', 'source_identifier', 'image_alt') as $key) {
            $data[$key] = sanitize_text_field($data[$key]);
        }
        $data['message'] = wp_kses_post($data['message']);
        $data['cta_url'] = esc_url_raw($data['cta_url'], array('http', 'https'));
        $data['image_url'] = esc_url_raw($data['image_url'], array('http', 'https'));
        $data['image_position'] = in_array($data['image_position'], array('start', 'end'), true) ? $data['image_position'] : 'start';
        foreach (array('background', 'foreground', 'accent') as $key) {
            $data[$key] = sanitize_hex_color($data[$key]) ?: $defaults[$key];
        }
        foreach (array('sticky', 'close_button', 'tracking', 'show_countdown') as $key) {
            $data[$key] = empty($data[$key]) ? 0 : 1;
        }
        $data['reappearance'] = in_array($data['reappearance'], array('refresh', 'session', 'after_hours', 'never'), true) ? $data['reappearance'] : 'after_hours';
        foreach (array('dismiss_hours', 'delay_ms', 'auto_hide_ms') as $key) {
            $data[$key] = max(0, absint($data[$key]));
        }
        foreach (array('start_at', 'end_at') as $key) {
            $data[$key] = preg_replace('/[^0-9T:+\- ]/', '', (string) $data[$key]);
        }
        foreach (array('include_paths', 'exclude_paths') as $key) {
            $parts = array_filter(array_map('trim', explode("\n", (string) $data[$key])));
            $data[$key] = implode("\n", array_map('sanitize_text_field', $parts));
        }
        return apply_filters('swairishx_sanitize_campaign', $data, $extension_input);
    }

    public function get_campaign($id) {
        $post = get_post(absint($id));
        if (!$post || self::POST_TYPE !== $post->post_type) {
            return null;
        }
        $data = get_post_meta($post->ID, self::META_KEY, true);
        return is_array($data) ? $this->sanitize_campaign($data) : array();
    }

    public function campaigns($status = array('publish', 'draft')) {
        return get_posts(array(
            'post_type' => self::POST_TYPE,
            'post_status' => $status,
            'posts_per_page' => 200,
            'orderby' => 'modified',
            'order' => 'DESC',
        ));
    }

    public function capture_event($source, $type, array $payload, $key = '') {
        global $wpdb;
        $source = sanitize_key($source);
        $type = sanitize_key($type);
        $key = $key ? sanitize_text_field($key) : hash('sha256', $source . '|' . $type . '|' . wp_json_encode($payload));
        $table = $wpdb->prefix . 'swairishx_events';
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Writes to the plugin's event table cannot use the object cache.
        return $wpdb->query(
            $wpdb->prepare(
                'INSERT IGNORE INTO %i (event_key,source,event_type,occurred_at,payload) VALUES (%s,%s,%s,%s,%s)',
                $table,
                $key,
                $source,
                $type,
                current_time('mysql', true),
                wp_json_encode($payload)
            )
        );
    }

    public function latest_event($source = '') {
        global $wpdb;
        $table = $wpdb->prefix . 'swairishx_events';
        if ($source) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reads the latest row from the plugin's event table.
            $row = $wpdb->get_row($wpdb->prepare('SELECT * FROM %i WHERE source=%s ORDER BY occurred_at DESC,id DESC LIMIT 1', $table, sanitize_key($source)), ARRAY_A);
        } else {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reads the latest row from the plugin's event table.
            $row = $wpdb->get_row($wpdb->prepare('SELECT * FROM %i ORDER BY occurred_at DESC,id DESC LIMIT 1', $table), ARRAY_A);
        }
        if (!$row) {
            return array();
        }
        $row['payload'] = json_decode($row['payload'], true);
        return $row;
    }

    public function record_stat($campaign_id, $event) {
        global $wpdb;
        $columns = array('impression' => 'impressions', 'click' => 'clicks', 'dismiss' => 'dismissals');
        if (!isset($columns[$event])) {
            return false;
        }
        $column = $columns[$event];
        $table = $wpdb->prefix . 'swairishx_stats';
        $date = current_time('Y-m-d', true);
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Atomic counter updates must write directly to the plugin's statistics table.
        return $wpdb->query(
            $wpdb->prepare(
                'INSERT INTO %i (campaign_id,stat_date,%i) VALUES (%d,%s,1) ON DUPLICATE KEY UPDATE %i=%i+1',
                $table,
                $column,
                absint($campaign_id),
                $date,
                $column,
                $column
            )
        );
    }

    public function report($days = 30) {
        global $wpdb;
        $table = $wpdb->prefix . 'swairishx_stats';
        $since = gmdate('Y-m-d', time() - DAY_IN_SECONDS * max(1, absint($days)));
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Aggregated reporting reads the plugin's statistics table.
        return $wpdb->get_results($wpdb->prepare('SELECT campaign_id,SUM(impressions) impressions,SUM(clicks) clicks,SUM(dismissals) dismissals FROM %i WHERE stat_date >= %s GROUP BY campaign_id', $table, $since), ARRAY_A);
    }
}
