<?php
/**
 * Kantan Analytics — Attribution Webhook + passthrough params.
 *
 * Pushes every conversion (with its first-party campaign + journey attribution)
 * to a user-configured webhook, keyed by the anonymous visitor_id Kantan already
 * injects into forms — so email/CRM tools (HubSpot, GoHighLevel, Salesforce, …)
 * can join it to the contact. PII-free by design. Fires on the single
 * `kantan_conversion_tracked` action, which covers every conversion type.
 *
 * @since 2.17.0 (feature/attribution-webhook)
 */

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

/* ───────────────────────────────────────────────────────────
 * Passthrough param allowlist (Option 3)
 * ─────────────────────────────────────────────────────────── */

/** Universal default allowlist of inbound click/contact ids worth capturing. */
function kantan_attribution_default_passthrough() {
    return array(
        // Ad click IDs
        'gclid', 'gbraid', 'wbraid', 'gclsrc', 'gad_source', 'dclid', 'msclkid',
        'yclid', 'ysclid', 'ymclid', 'fbclid', 'ttclid', 'twclid', 'li_fat_id',
        'sccid', 'snap_cid', 'rdt_cid', 'epik', 'pin_cid', 'spclid', 'qclid',
        'awc', 'adct', 'ob_click_id', 'tbclid', 'tblci', 'tduid',
        // Email / CRM / marketing-automation
        'mc_cid', 'mc_eid', '_kx', 'mkt_tok', '_hsenc', '_hsmi',
        'hsa_cam', 'hsa_grp', 'hsa_ad', 'hsa_src', 'hsa_net', 'hsa_ver',
        'hsa_acc', 'hsa_kw', 'hsa_mt', 'hsa_tgt',
        'vero_id', 'vero_conv', 'vgo_ee', 'sfmc_id', 'sfmc_sub', 'contact_id',
    );
}

/** Resolved allowlist (lowercased) — the saved option, or the default if unset. */
function kantan_attribution_passthrough_params() {
    $raw = get_option('kantan_passthrough_params', '');
    if ($raw === '' || $raw === false) {
        return kantan_attribution_default_passthrough();
    }
    $parts = array_filter(array_map(function ($p) { return strtolower(trim($p)); }, explode(',', $raw)));
    return array_values(array_unique($parts));
}

/* ───────────────────────────────────────────────────────────
 * Schema: ensure wp_kantan_utm.passthrough exists on existing installs.
 * dbDelta is unreliable for ALTER, so check INFORMATION_SCHEMA + ALTER directly.
 * ─────────────────────────────────────────────────────────── */
function kantan_attribution_ensure_passthrough_column() {
    if (get_option('kantan_utm_passthrough_col')) {
        return;
    }
    global $wpdb;
    $table = $wpdb->prefix . 'kantan_utm';
    $has = (int) $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
         WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND COLUMN_NAME = 'passthrough'",
        $table
    ));
    if (!$has) {
        $wpdb->query("ALTER TABLE $table ADD COLUMN passthrough text DEFAULT NULL");
    }
    update_option('kantan_utm_passthrough_col', 1);
}
add_action('init', 'kantan_attribution_ensure_passthrough_column', 1);

/* ───────────────────────────────────────────────────────────
 * Config + secret storage
 * ─────────────────────────────────────────────────────────── */

function kantan_attribution_set_secret($value) {
    $value = (string) $value;
    if (get_option('kantan_webhook_secret') === false) {
        add_option('kantan_webhook_secret', $value, '', false); // non-autoloaded
    } else {
        update_option('kantan_webhook_secret', $value, false);
    }
}

/** Public config — never returns the signing secret itself. */
function kantan_attribution_config() {
    return array(
        'enabled'            => (bool) get_option('kantan_webhook_enabled', false),
        'url'                => (string) get_option('kantan_webhook_url', ''),
        'secret_set'         => (string) get_option('kantan_webhook_secret', '') !== '',
        'passthrough_params' => implode(', ', kantan_attribution_passthrough_params()),
        'recent'             => array_slice((array) get_option('kantan_webhook_log', array()), 0, 20),
    );
}

/** Append a delivery to the capped recent-deliveries log. */
function kantan_attribution_log($type, $status) {
    $log = (array) get_option('kantan_webhook_log', array());
    array_unshift($log, array('ts' => gmdate('c'), 'type' => (string) $type, 'status' => (string) $status));
    update_option('kantan_webhook_log', array_slice($log, 0, 20), false);
}

/* ───────────────────────────────────────────────────────────
 * Payload + delivery
 * ─────────────────────────────────────────────────────────── */

/** MySQL datetime (stored UTC) → ISO-8601. */
function kantan_attribution_iso($dt) {
    if (empty($dt)) {
        return gmdate('c');
    }
    $ts = strtotime($dt . ' UTC');
    return $ts ? gmdate('c', $ts) : gmdate('c');
}

/** Build the conversion payload, enriched from the DB by session_id. */
function kantan_attribution_build_payload($session_id) {
    global $wpdb;
    $session_id = sanitize_text_field($session_id);
    $a  = $wpdb->prefix . 'kantan_analytics';
    $u  = $wpdb->prefix . 'kantan_utm';
    $c  = $wpdb->prefix . 'kantan_conversions';
    $pv = $wpdb->prefix . 'kantan_pageviews';

    $conv = $wpdb->get_row($wpdb->prepare(
        "SELECT conversion_type, conversion_label, conversion_value, page_url, visitor_id, converted_at
         FROM $c WHERE session_id = %s ORDER BY id DESC LIMIT 1",
        $session_id
    ));
    $sess = $wpdb->get_row($wpdb->prepare(
        "SELECT visitor_id, traffic_source, referrer, page_url, visit_date
         FROM $a WHERE session_id = %s ORDER BY id ASC LIMIT 1",
        $session_id
    ));
    $utm = $wpdb->get_row($wpdb->prepare(
        "SELECT utm_source, utm_medium, utm_campaign, utm_term, utm_content, passthrough
         FROM $u WHERE session_id = %s LIMIT 1",
        $session_id
    ));
    $journey = $wpdb->get_results($wpdb->prepare(
        "SELECT page_url, entered_at FROM $pv WHERE session_id = %s ORDER BY sequence_num ASC LIMIT 10",
        $session_id
    ));

    $visitor_id = ($conv && !empty($conv->visitor_id)) ? $conv->visitor_id : (isset($sess->visitor_id) ? $sess->visitor_id : '');

    $passthrough = array();
    if ($utm && !empty($utm->passthrough)) {
        $decoded = json_decode($utm->passthrough, true);
        if (is_array($decoded)) {
            $passthrough = $decoded;
        }
    }

    return array(
        'event'       => 'conversion',
        'type'        => $conv ? $conv->conversion_type : '',
        'label'       => $conv ? $conv->conversion_label : '',
        'occurred_at' => kantan_attribution_iso($conv ? $conv->converted_at : null),
        'site_url'    => home_url(),
        'visitor_id'  => $visitor_id,
        'session_id'  => $session_id,
        'page_url'    => $conv && $conv->page_url ? $conv->page_url : (isset($sess->page_url) ? $sess->page_url : ''),
        'attribution' => array(
            'traffic_source' => isset($sess->traffic_source) ? $sess->traffic_source : '',
            'referrer'       => isset($sess->referrer) ? $sess->referrer : '',
            'utm'            => array(
                'source'   => $utm ? (string) $utm->utm_source : '',
                'medium'   => $utm ? (string) $utm->utm_medium : '',
                'campaign' => $utm ? (string) $utm->utm_campaign : '',
                'term'     => $utm ? (string) $utm->utm_term : '',
                'content'  => $utm ? (string) $utm->utm_content : '',
            ),
            'passthrough'    => $passthrough,
        ),
        'journey'     => array_map(function ($p) {
            return array('page_url' => $p->page_url, 'ts' => kantan_attribution_iso($p->entered_at));
        }, $journey ? $journey : array()),
    );
}

/**
 * Deliver a payload. Production calls are non-blocking (never slow the visitor);
 * the "Send test" path is blocking so the user gets a real HTTP result.
 */
function kantan_attribution_send($payload, $blocking = false) {
    $url = (string) get_option('kantan_webhook_url', '');
    if ($url === '') {
        return array('ok' => false, 'error' => 'No webhook URL configured.');
    }

    $body    = wp_json_encode($payload);
    $secret  = (string) get_option('kantan_webhook_secret', '');
    $headers = array('Content-Type' => 'application/json');
    if ($secret !== '') {
        $headers['X-Kantan-Signature'] = 'sha256=' . hash_hmac('sha256', $body, $secret);
    }

    $res = wp_remote_post($url, array(
        'headers'     => $headers,
        'body'        => $body,
        'data_format' => 'body',
        'timeout'     => $blocking ? 12 : 5,
        'blocking'    => (bool) $blocking,
        'redirection' => 3,
    ));

    $type = isset($payload['type']) ? $payload['type'] : 'conversion';

    if (!$blocking) {
        // Fire-and-forget: no response to read.
        kantan_attribution_log($type, 'sent');
        return array('ok' => true, 'sent' => true);
    }
    if (is_wp_error($res)) {
        $msg = 'Request failed: ' . $res->get_error_message();
        kantan_attribution_log($type, $msg);
        return array('ok' => false, 'error' => $msg);
    }
    $code = (int) wp_remote_retrieve_response_code($res);
    kantan_attribution_log($type, (string) $code);
    return array('ok' => ($code >= 200 && $code < 300), 'code' => $code);
}

/** Representative payload for the Send-test button. */
function kantan_attribution_sample_payload() {
    return array(
        'event'       => 'conversion',
        'type'        => 'test',
        'label'       => 'Kantan webhook test',
        'occurred_at' => gmdate('c'),
        'site_url'    => home_url(),
        'visitor_id'  => 'kv_test_1777991140130',
        'session_id'  => 'ks_test',
        'page_url'    => home_url('/contact/'),
        'attribution' => array(
            'traffic_source' => 'organic',
            'referrer'       => 'https://www.google.com/',
            'utm'            => array('source' => 'newsletter', 'medium' => 'email', 'campaign' => 'june-promo', 'term' => '', 'content' => ''),
            'passthrough'    => array('contact_id' => 'sample-contact-123', 'gclid' => 'sample-gclid'),
        ),
        'journey'     => array(
            array('page_url' => home_url('/'), 'ts' => gmdate('c')),
            array('page_url' => home_url('/pricing/'), 'ts' => gmdate('c')),
        ),
    );
}

/* The single trigger — covers every conversion type (AJAX + form paths). */
function kantan_attribution_on_conversion($session_id, $type, $ctx) {
    if (!get_option('kantan_webhook_enabled', false) || (string) get_option('kantan_webhook_url', '') === '') {
        return;
    }
    $payload = kantan_attribution_build_payload($session_id);
    kantan_attribution_send($payload, false); // non-blocking
}
add_action('kantan_conversion_tracked', 'kantan_attribution_on_conversion', 10, 3);

/* ───────────────────────────────────────────────────────────
 * REST: config (GET/PUT) + test (POST). manage_options + wp_rest nonce.
 * ─────────────────────────────────────────────────────────── */
add_action('rest_api_init', function () {
    $ns = 'kantan/v1';
    register_rest_route($ns, '/webhook/config', array(
        array('methods' => 'GET', 'callback' => 'kantan_attribution_rest_get', 'permission_callback' => 'kantan_rest_admin_permission'),
        array('methods' => 'PUT', 'callback' => 'kantan_attribution_rest_save', 'permission_callback' => 'kantan_rest_permission'),
    ));
    register_rest_route($ns, '/webhook/test', array(
        'methods' => 'POST', 'callback' => 'kantan_attribution_rest_test', 'permission_callback' => 'kantan_rest_permission',
    ));
});

function kantan_attribution_rest_get() {
    return rest_ensure_response(kantan_attribution_config());
}

function kantan_attribution_rest_save($request) {
    $b = $request->get_json_params();
    if (!is_array($b)) {
        $b = array();
    }
    if (isset($b['enabled'])) {
        update_option('kantan_webhook_enabled', (bool) $b['enabled']);
    }
    if (isset($b['url'])) {
        update_option('kantan_webhook_url', esc_url_raw(trim((string) $b['url'])));
    }
    if (isset($b['passthrough_params'])) {
        update_option('kantan_passthrough_params', sanitize_text_field($b['passthrough_params']));
    }
    if (!empty($b['clear_secret'])) {
        kantan_attribution_set_secret('');
    } elseif (isset($b['secret']) && trim((string) $b['secret']) !== '') {
        kantan_attribution_set_secret(sanitize_text_field($b['secret']));
    }
    return rest_ensure_response(array('saved' => true, 'config' => kantan_attribution_config()));
}

function kantan_attribution_rest_test($request) {
    if ((string) get_option('kantan_webhook_url', '') === '') {
        return rest_ensure_response(array('ok' => false, 'error' => 'Set and save a webhook URL first.'));
    }
    $res = kantan_attribution_send(kantan_attribution_sample_payload(), true); // blocking for feedback
    $res['config'] = kantan_attribution_config();
    return rest_ensure_response($res);
}
