<?php

declare(strict_types=1);

namespace Numinix\SeekmodoWordPress\Gateway;

/**
 * Per-shopper session + request context for gateway search / events.
 *
 * BrowserToken's gateway `session_id` is deliberately site-scoped (HTML
 * cache safety) and must NOT be reused as a shopper telemetry sid —
 * that would collapse every visitor onto one session.
 *
 * Priority for session id:
 *   1. Existing first-party `sm_sid` cookie
 *   2. WooCommerce customer session key when available
 *   3. Fresh random id (persisted to `sm_sid`)
 */
final class ShopperContext
{
    private const COOKIE = 'sm_sid';
    private const SEID_COOKIE = 'sm_seid';
    private const COOKIE_TTL = 86400 * 30;
    private const SEID_COOKIE_TTL = 86400;

    public static function sessionId(): string
    {
        $fromCookie = self::cookieSid();
        if ($fromCookie !== '') {
            return $fromCookie;
        }

        if (function_exists('WC') && is_object(WC()) && isset(WC()->session) && is_object(WC()->session)) {
            $wc = WC()->session;
            if (method_exists($wc, 'get_customer_id')) {
                $cid = (string) $wc->get_customer_id();
                if ($cid !== '') {
                    self::persistCookie($cid);

                    return substr($cid, 0, 64);
                }
            }
        }

        $fresh = self::randomSid();
        self::persistCookie($fresh);

        return $fresh;
    }

    /**
     * Shopper fields for /v1/search and /v1/events payloads.
     *
     * @return array{session_id:string,ua:string,ip:string,referer:string}
     */
    public static function forGateway(): array
    {
        return [
            'session_id' => self::sessionId(),
            'ua'         => self::userAgent(),
            'ip'         => self::clientIp(),
            'referer'    => self::referer(),
        ];
    }

    public static function userAgent(): string
    {
        $ua = isset($_SERVER['HTTP_USER_AGENT']) ? (string) $_SERVER['HTTP_USER_AGENT'] : '';

        return substr($ua, 0, 512);
    }

    public static function clientIp(): string
    {
        $candidates = [];
        if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
            $candidates[] = (string) $_SERVER['HTTP_CF_CONNECTING_IP'];
        }
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $candidates[] = (string) $_SERVER['HTTP_X_FORWARDED_FOR'];
        }
        if (!empty($_SERVER['REMOTE_ADDR'])) {
            $candidates[] = (string) $_SERVER['REMOTE_ADDR'];
        }
        foreach ($candidates as $raw) {
            $ip = trim(explode(',', $raw, 2)[0]);
            if ($ip !== '' && filter_var($ip, FILTER_VALIDATE_IP)) {
                return substr($ip, 0, 45);
            }
        }

        return '';
    }

    public static function referer(): string
    {
        $ref = isset($_SERVER['HTTP_REFERER']) ? (string) $_SERVER['HTTP_REFERER'] : '';

        return substr($ref, 0, 255);
    }

    /**
     * Persist the latest SERP search_event_id so the click beacon can
     * still attribute when footer JS races or a cached SERP omits the
     * inline SEEKMODO_BEACON.search_event_id assignment.
     */
    public static function rememberSearchEventId(int $searchEventId): void
    {
        if ($searchEventId <= 0 || headers_sent()) {
            return;
        }
        $secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
            || (isset($_SERVER['SERVER_PORT']) && (int) $_SERVER['SERVER_PORT'] === 443);
        setcookie(self::SEID_COOKIE, (string) $searchEventId, [
            'expires'  => time() + self::SEID_COOKIE_TTL,
            'path'     => '/',
            'secure'   => $secure,
            'httponly' => false, // beacon.js must read it
            'samesite' => 'Lax',
        ]);
        $_COOKIE[self::SEID_COOKIE] = (string) $searchEventId;
    }

    public static function lastSearchEventId(): int
    {
        if (!isset($_COOKIE[self::SEID_COOKIE])) {
            return 0;
        }

        return max(0, (int) $_COOKIE[self::SEID_COOKIE]);
    }

    private static function cookieSid(): string
    {
        if (!isset($_COOKIE[self::COOKIE]) || !is_string($_COOKIE[self::COOKIE])) {
            return '';
        }
        $sid = preg_replace('/[^A-Za-z0-9_-]/', '', $_COOKIE[self::COOKIE]) ?? '';
        if (strlen($sid) < 8) {
            return '';
        }

        return substr($sid, 0, 64);
    }

    private static function persistCookie(string $sid): void
    {
        $sid = substr(preg_replace('/[^A-Za-z0-9_-]/', '', $sid) ?? '', 0, 64);
        if ($sid === '' || headers_sent()) {
            return;
        }
        $secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
            || (isset($_SERVER['SERVER_PORT']) && (int) $_SERVER['SERVER_PORT'] === 443);
        setcookie(self::COOKIE, $sid, [
            'expires'  => time() + self::COOKIE_TTL,
            'path'     => '/',
            'secure'   => $secure,
            'httponly' => true,
            'samesite' => 'Lax',
        ]);
        $_COOKIE[self::COOKIE] = $sid;
    }

    private static function randomSid(): string
    {
        try {
            return rtrim(strtr(base64_encode(random_bytes(18)), '+/', '-_'), '=');
        } catch (\Throwable $e) {
            return substr(hash('sha256', uniqid('sm', true)), 0, 24);
        }
    }
}
