<?php
namespace EasyChat\Library\Cms;








final class CmsVisitorIdentityAssertion
{
    const VERSION = 1;
    const MAX_TOKEN_BYTES = 4096;
    const DEFAULT_TTL = 90;
    const MAX_TTL = 180;
    const CLOCK_SKEW = 20;

    public static function issue(array $identity, string $widget_session_id, string $install_secret, int $now = 0): string
    {
        $widget_session_id = self::sessionId($widget_session_id);
        $install_secret = trim($install_secret);
        $provider = self::provider((string)($identity['provider'] ?? ''));
        $subject = self::plain((string)($identity['subject'] ?? ''), 191);
        $scope = self::plain((string)($identity['scope'] ?? 'default'), 191);
        if ($install_secret === '' || $widget_session_id === '' || $provider === '' || $subject === '') {
            return '';
        }

        $now = $now > 0 ? $now : time();
        $ttl = max(30, min(self::MAX_TTL, (int)($identity['ttl'] ?? self::DEFAULT_TTL)));
        $payload = array(
            'v' => self::VERSION,
            'iss' => $provider,
            'sub' => $subject,
            'scope' => $scope !== '' ? $scope : 'default',
            'sid' => hash_hmac('sha256', $widget_session_id, $install_secret),
            'iat' => $now,
            'exp' => $now + $ttl,
            'jti' => bin2hex(random_bytes(12)),
            'name' => self::plain((string)($identity['name'] ?? ''), 255),
            'email' => self::email((string)($identity['email'] ?? '')),
            'phone' => self::plain((string)($identity['phone'] ?? ''), 64),
        );
        $json = (string)json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        $encoded = self::encryptPayload($json, $install_secret);
        if ($encoded === '') return '';
        $signature = self::base64UrlEncode(hash_hmac('sha256', 'easychat.cms-visitor.v1.' . $encoded, $install_secret, true));
        return 'ecv1.' . $encoded . '.' . $signature;
    }

    public static function verify(string $token, string $widget_session_id, string $install_secret, int $now = 0): array
    {
        $token = trim($token);
        $widget_session_id = self::sessionId($widget_session_id);
        $install_secret = trim($install_secret);
        if ($token === '' || strlen($token) > self::MAX_TOKEN_BYTES || $widget_session_id === '' || $install_secret === '') {
            return array('valid' => false, 'error' => 'identity_assertion_invalid');
        }
        $parts = explode('.', $token);
        if (count($parts) !== 3 || $parts[0] !== 'ecv1') {
            return array('valid' => false, 'error' => 'identity_assertion_format');
        }
        $expected = self::base64UrlEncode(hash_hmac('sha256', 'easychat.cms-visitor.v1.' . $parts[1], $install_secret, true));
        if (!hash_equals($expected, $parts[2])) {
            return array('valid' => false, 'error' => 'identity_assertion_signature');
        }
        $raw = self::decryptPayload($parts[1], $install_secret);
        $payload = $raw !== '' ? json_decode($raw, true) : null;
        if (!is_array($payload) || (int)($payload['v'] ?? 0) !== self::VERSION) {
            return array('valid' => false, 'error' => 'identity_assertion_payload');
        }
        $provider = self::provider((string)($payload['iss'] ?? ''));
        $subject = self::plain((string)($payload['sub'] ?? ''), 191);
        $scope = self::plain((string)($payload['scope'] ?? ''), 191);
        $iat = (int)($payload['iat'] ?? 0);
        $exp = (int)($payload['exp'] ?? 0);
        $now = $now > 0 ? $now : time();
        if ($provider === '' || $subject === '' || $scope === '' || $iat <= 0 || $exp <= $iat || ($exp - $iat) > self::MAX_TTL) {
            return array('valid' => false, 'error' => 'identity_assertion_claims');
        }
        if ($iat > $now + self::CLOCK_SKEW || $exp < $now - self::CLOCK_SKEW) {
            return array('valid' => false, 'error' => 'identity_assertion_expired');
        }
        $expected_sid = hash_hmac('sha256', $widget_session_id, $install_secret);
        if (!hash_equals($expected_sid, (string)($payload['sid'] ?? ''))) {
            return array('valid' => false, 'error' => 'identity_assertion_session');
        }

        return array('valid' => true, 'identity' => array(
            'provider' => $provider,
            'subject' => $subject,
            'scope' => $scope,
            'name' => self::plain((string)($payload['name'] ?? ''), 255),
            'email' => self::email((string)($payload['email'] ?? '')),
            'phone' => self::plain((string)($payload['phone'] ?? ''), 64),
            'issued_at' => $iat,
            'expires_at' => $exp,
        ));
    }

    private static function sessionId(string $value): string
    {
        $value = trim($value);
        return preg_match('/^[a-f0-9]{32}$/i', $value) ? strtolower($value) : '';
    }

    private static function provider(string $value): string
    {
        $value = strtolower(trim($value));
        return preg_match('/^[a-z0-9_-]{2,32}$/', $value) ? $value : '';
    }

    private static function email(string $value): string
    {
        $value = function_exists('mb_strtolower') ? mb_strtolower(trim($value), 'UTF-8') : strtolower(trim($value));
        return $value !== '' && filter_var($value, FILTER_VALIDATE_EMAIL) ? self::substring($value, 254) : '';
    }

    private static function plain(string $value, int $limit): string
    {
        $value = trim(preg_replace('/[\x00-\x1F\x7F]+/u', ' ', $value));
        return self::substring(preg_replace('/\s+/u', ' ', $value), $limit);
    }

    private static function substring(string $value, int $limit): string
    {
        return function_exists('mb_substr') ? mb_substr($value, 0, $limit, 'UTF-8') : substr($value, 0, $limit);
    }

    private static function base64UrlEncode(string $value): string
    {
        return rtrim(strtr(base64_encode($value), '+/', '-_'), '=');
    }

    private static function base64UrlDecode(string $value): string
    {
        if ($value === '' || !preg_match('/^[A-Za-z0-9_-]+$/', $value)) return '';
        $decoded = base64_decode(strtr($value, '-_', '+/') . str_repeat('=', (4 - strlen($value) % 4) % 4), true);
        return is_string($decoded) ? $decoded : '';
    }

    private static function encryptPayload(string $plaintext, string $secret): string
    {
        if ($plaintext === '' || !function_exists('openssl_encrypt')) return '';
        $nonce = random_bytes(12);
        $tag = '';
        $ciphertext = openssl_encrypt($plaintext, 'aes-256-gcm', self::encryptionKey($secret), OPENSSL_RAW_DATA, $nonce, $tag, 'easychat.cms-visitor.v1', 16);
        return is_string($ciphertext) && strlen($tag) === 16 ? self::base64UrlEncode($nonce . $tag . $ciphertext) : '';
    }

    private static function decryptPayload(string $encoded, string $secret): string
    {
        if (!function_exists('openssl_decrypt')) return '';
        $envelope = self::base64UrlDecode($encoded);
        if (strlen($envelope) < 29) return '';
        $plaintext = openssl_decrypt(substr($envelope, 28), 'aes-256-gcm', self::encryptionKey($secret), OPENSSL_RAW_DATA, substr($envelope, 0, 12), substr($envelope, 12, 16), 'easychat.cms-visitor.v1');
        return is_string($plaintext) ? $plaintext : '';
    }

    private static function encryptionKey(string $secret): string
    {
        return hash_hmac('sha256', 'easychat.cms-visitor.encryption.v1', $secret, true);
    }
}
