<?php

namespace ZenoChatbot\Helper;


use ZenoChatbot\App;
use ZenoChatbot\Framework\Support\Arr;

class Helper
{
    private const SESSION_COOKIE_NAME = 'zeno_chatbot_session';
    private const SESSION_COOKIE_TTL = 86400;
    private const SESSION_SIGN_VERSION = 2;

    public static function getClientIp($cached = true)
    {
        return IpDetector::getClientIp($cached);
    }

    /**
     * @throws \Exception
     */
    public static function getSessionKey()
    {
        return SessionKeyGenerator::generateSessionId();
    }

    public static function formatTimeHM($date)
    {
        if ($date instanceof \DateTime) {
            return $date->format('H:i');
        }

        return gmdate('H:i', strtotime((string)$date));
    }

    private static function cookieOptions(int $ttl, bool $httpOnly = false): array
    {
        $cookiePath = defined('COOKIEPATH') ? COOKIEPATH : '/';
        $cookieDomain = defined('COOKIE_DOMAIN') ? COOKIE_DOMAIN : '';
        $expiry = time() + $ttl;

        return [
            'expires'  => $expiry,
            'path'     => $cookiePath,
            'domain'   => $cookieDomain,
            'secure'   => is_ssl(),
            'httponly' => $httpOnly,
            'samesite' => 'Lax',
        ];
    }

    public static function setCookie($name, $value, $ttl = null)
    {
        $ttl = $ttl ?: self::SESSION_COOKIE_TTL;
        $options = static::cookieOptions((int) $ttl, false);

        if (PHP_VERSION_ID <= 70300) {
            return setcookie(
                $name,
                $value,
                $options['expires'],
                $options['path'],
                $options['domain'],
                $options['secure'],
                $options['httponly']
            );
        }

        return setcookie($name, $value, $options);
    }

    public static function clearCookieData($name)
    {
        $options = static::cookieOptions(-3600, false);

        if (PHP_VERSION_ID <= 70300) {
            return setcookie(
                $name,
                '',
                $options['expires'],
                $options['path'],
                $options['domain'],
                $options['secure'],
                $options['httponly']
            );
        }

        return setcookie($name, '', $options);
    }

    /**
     * Safely get data from a cookie with proper WordPress sanitization.
     *
     * @param string        $name              Cookie key.
     * @param callable|null $sanitizeCallback  Optional sanitizer. Defaults to sanitize_text_field.
     *
     * @return string|null Sanitized cookie value or null if not found/invalid.
     */
    public static function getDataFromCookie(string $name, ?callable $sanitizeCallback = null): ?string
    {
        // Check if cookie exists
        if (!isset($_COOKIE[$name])) {
            return null;
        }

        // Get raw cookie value - wp_unslash is the WordPress-recommended way to handle superglobals
        // that may have been automatically slashed by WordPress
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
        $raw = wp_unslash($_COOKIE[$name]);

        // Validate that we have a string value
        if (!is_string($raw)) {
            return null;
        }

        // Ensure string type for safety
        $raw = (string) $raw;

        // Apply custom sanitization callback if provided and callable
        if ($sanitizeCallback && is_callable($sanitizeCallback)) {
            $sanitized = $sanitizeCallback($raw);
            // Ensure sanitization callback returned a valid result
            return is_string($sanitized) ? $sanitized : null;
        }

        // Default WordPress sanitization for text fields
        return sanitize_text_field($raw);
    }

    /**
     * Base64 sanitizer for cookies with strict validation.
     *
     * @param string $value Raw cookie value to validate as base64.
     * @return string|null Valid base64 string or null if invalid.
     */
    public static function sanitizeBase64(string $value): ?string
    {
        // First sanitize as text field to remove any dangerous characters
        $sanitized = sanitize_text_field($value);

        // Validate base64 format (letters, numbers, +, /, = padding)
        if (!preg_match('/^[A-Za-z0-9\/\+=]+$/', $sanitized)) {
            return null;
        }

        // Additional validation: try to decode to ensure it's valid base64
        $decoded = base64_decode($sanitized, true);
        if ($decoded === false) {
            return null;
        }

        return $sanitized;
    }

    public static function generateSessionKey($customerId = 'customer')
    {
        return $customerId . '_' . wp_generate_password(24, false, false) . '_' . time();
    }

    public static function isTimeExpired($timestamp, $ttlSeconds)
    {
        if (!is_numeric($timestamp)) {
            return true;
        }

        return (time() - (int)$timestamp) > (int)$ttlSeconds;
    }

    public static function getSessionData()
    {
        $sessionData = Helper::getDataFromCookie(self::SESSION_COOKIE_NAME, [Helper::class, 'sanitizeBase64']);

        if (!$sessionData) {
            return static::getDefaultSessionValue();
        }

        $decoded = static::decodeSessionCookie($sessionData);
        $decodedData = $decoded['data'] ?? null;

        if (!is_array($decodedData)) {
            static::clearCookieData(self::SESSION_COOKIE_NAME);
            return static::getDefaultSessionValue();
        }

        $decodedData = wp_parse_args(
            Arr::only($decodedData, array_keys(static::getDefaultSessionValue())),
            static::getDefaultSessionValue()
        );

        // check if the session is valid
        $createdAt = Arr::get($decodedData, 'created_at', 0);
        $isExpired = static::isTimeExpired($createdAt, self::SESSION_COOKIE_TTL);

        if ($createdAt && $isExpired) {
            Helper::clearCookieData(self::SESSION_COOKIE_NAME);
            return static::getDefaultSessionValue();
        }

        if (($decoded['version'] ?? 1) < self::SESSION_SIGN_VERSION) {
            // Seamless migration for existing unsigned sessions.
            static::updateSession($decodedData);
        }

        $sessionValue = Arr::only($decodedData, ['session_id', 'conversation_id', 'customer_id', 'created_at', 'last_activity']);
        return wp_parse_args($sessionValue, static::getDefaultSessionValue());
    }

    public static function updateSession($data = [])
    {
        $data = wp_parse_args(Arr::only((array) $data, array_keys(static::getDefaultSessionValue())), static::getDefaultSessionValue());
        $now = time();
        $data['created_at'] = !empty($data['created_at']) ? (int) $data['created_at'] : $now;
        $data['last_activity'] = $now;
        $data['conversation_id'] = !empty($data['conversation_id']) ? (int) $data['conversation_id'] : null;
        $data['customer_id'] = !empty($data['customer_id']) ? (int) $data['customer_id'] : null;
        $data['session_id'] = !empty($data['session_id']) ? sanitize_text_field((string) $data['session_id']) : null;

        $encoded = static::encodeSignedSession($data);

        return static::setCookie(self::SESSION_COOKIE_NAME, $encoded, self::SESSION_COOKIE_TTL);
    }

    public static function getDefaultSessionValue()
    {
        return [
            'session_id'      => null,
            'conversation_id' => null,
            'customer_id'     => null,
            'created_at'      => null,
            'last_activity'   => null
        ];
    }

    private static function sessionSigningKey(): string
    {
        return wp_salt('auth');
    }

    private static function signaturePayload(array $data): string
    {
        return hash_hmac('sha256', wp_json_encode($data), static::sessionSigningKey());
    }

    private static function encodeSignedSession(array $data): string
    {
        $payload = [
            'v' => self::SESSION_SIGN_VERSION,
            'd' => $data,
        ];
        $payload['sig'] = static::signaturePayload($payload);

        return base64_encode(wp_json_encode($payload));
    }

    private static function decodeSessionCookie(string $encoded): array
    {
        $decodedJson = base64_decode($encoded, true);
        if ($decodedJson === false) {
            return ['data' => null, 'version' => null];
        }

        $decoded = json_decode($decodedJson, true);
        if (!is_array($decoded)) {
            return ['data' => null, 'version' => null];
        }

        // New signed payload format.
        if (isset($decoded['v'], $decoded['d'], $decoded['sig']) && is_array($decoded['d'])) {
            $expectedSig = static::signaturePayload([
                'v' => (int) $decoded['v'],
                'd' => $decoded['d'],
            ]);
            if (!hash_equals($expectedSig, (string) $decoded['sig'])) {
                return ['data' => null, 'version' => null];
            }

            return ['data' => $decoded['d'], 'version' => (int) $decoded['v']];
        }

        // Legacy unsigned payload format.
        if (Arr::has($decoded, ['conversation_id', 'session_id'])) {
            return ['data' => $decoded, 'version' => 1];
        }

        return ['data' => null, 'version' => null];
    }

    public static function getSessionUserContext()
    {
        $request = App::request();

        $context = [
            'type'    => 'public',
            'user_id' => is_user_logged_in() ? get_current_user_id() : null,
            'name'    => $request->getSafe('name', 'sanitize_text_field') ?: '',
            'email'   => $request->getSafe('email', 'sanitize_email') ?: '',
        ];

        if (is_user_logged_in()) {
            $context['type'] = 'authenticated';
            $context['name'] = wp_get_current_user()->display_name ?: wp_get_current_user()->user_login;
            $context['email'] = wp_get_current_user()->user_email;
        }

        return $context;

    }
}
