<?php
namespace EasyChat\Library\Messenger;

use EasyChat\Library\AttachmentPolicy;
use PhpWA\Client;

 
final class WhatsAppPersonalProvider implements MessengerProviderInterface {
    public const PROVIDER = 'whatsapp-personal';

    private $registry;

    public function __construct($registry = null) {
        $this->registry = $registry;
        WhatsAppPersonalRuntime::requireAutoload();
    }

    public function providerCode(): string {
        return self::PROVIDER;
    }

    public function getBotInfo(string $token): array {
        return ['success' => false, 'error' => 'webhook_not_supported'];
    }

    public function syncWebhook(array $channel, string $token, string $webhook_url, string $secret): array {
        return ['success' => false, 'error' => 'webhook_not_supported'];
    }

     
    public function parseWebhook(array $payload): array {
        $event = is_array($payload['_phpwa_event'] ?? null) ? $payload['_phpwa_event'] : [];
        $channel_id = (int)($payload['_easychat_channel_id'] ?? 0);
        if ($event === [] || $channel_id <= 0 || empty($payload['_easychat_trusted_worker'])) {
            return ['update_type' => 'invalid', 'external_user_id' => ''];
        }

        $chat_jid = $this->jid((string)($event['chat_jid'] ?? ''));
        $sender_jid = $this->jid((string)($event['sender_jid'] ?? $event['participant_jid'] ?? ''));
        $alternate_jid = $this->jid((string)($event['alternate_sender_jid'] ?? ''));
        if ($chat_jid === '') {
            return ['update_type' => 'invalid', 'external_user_id' => ''];
        }

        $group = $this->isGroup($chat_jid);
        $direction = !empty($event['from_me']) ? 'out' : 'in';
        $identities = new WhatsAppPersonalIdentityRepository($this->registry);
        $resolved = $identities->resolve($channel_id, $chat_jid);
        $sender_profile = $sender_jid !== '' ? $identities->resolve($channel_id, $sender_jid) : [];
        $canonical_jid = $group
            ? $chat_jid
            : (string)($resolved['lid_jid'] ?? '');
        if (!$group && $canonical_jid === '') {
            $canonical_jid = $this->lidJid($chat_jid) ?: $this->lidJid($sender_jid) ?: $this->lidJid($alternate_jid);
        }
        if ($canonical_jid === '') {
            $canonical_jid = $chat_jid;
        }

        $display_name = trim((string)($payload['_easychat_display_name'] ?? $resolved['display_name'] ?? $event['push_name'] ?? ''));
        if ($display_name === '') {
            $display_name = $this->maskedLabel($channel_id, $canonical_jid, $identities, $sender_jid, $alternate_jid);
        }
        $timestamp = $this->timestamp($event['timestamp'] ?? 0);
        $external_message_id = $this->messageId((string)($event['id'] ?? ''));
        if ($external_message_id === '') {
            return ['update_type' => 'invalid', 'external_user_id' => ''];
        }
        $kind = strtolower((string)($event['kind'] ?? 'unknown'));
        $text = trim((string)($event['text'] ?? ''));
        $attachments = is_array($payload['_easychat_attachments'] ?? null) ? $payload['_easychat_attachments'] : [];
        $media_pending = !empty($payload['_easychat_media_pending']);
        if ($text === '' && !$attachments && !$media_pending) {
            $text = $this->unsupportedText($kind);
        }

        $history = !empty($payload['_easychat_history']);
        $channel = (new \EasyChat\Model\Messenger($this->registry))->getChannelById($channel_id);
        $account_id = $this->jid((string)($channel['external_account_id'] ?? ''));
        $sender_display_name = mb_substr(trim((string)(
            ($event['push_name'] ?? '') ?: ($sender_profile['display_name'] ?? '')
        )), 0, 191, 'UTF-8');
        $metadata = [
            'chat_type' => $group ? 'GROUP' : 'DIALOG',
            'whatsapp_chat_jid' => $chat_jid,
            'whatsapp_sender_jid' => $sender_jid,
            'whatsapp_identity_jid' => $canonical_jid,
            'whatsapp_message_kind' => $kind,
            'whatsapp_offline' => !empty($event['offline']) ? 1 : 0,
            'sender_display_name' => $sender_display_name,
            'sender_resolution_status' => $group && $sender_display_name === '' ? 'provider_unavailable' : '',
            'chat_display_name' => $display_name,
             
             
            'whatsapp_account_hash' => $account_id !== '' ? hash('sha256', $account_id) : '',
        ];
        if ($history) {
            $metadata['sync_origin'] = 'history_reconciliation';
        }
        if (!empty($payload['_easychat_media_download_failed'])) {
             
             
            $metadata['whatsapp_media_download_status'] = 'unavailable';
        }
        $phone = $direction === 'in' && !$group
            ? $identities->confirmedPhone($channel_id, $sender_jid, $alternate_jid)
            : '';
        if ($phone !== '') {
            $metadata['provider_confirmed_phone'] = $phone;
            $metadata['provider_phone_evidence'] = 'whatsapp_protocol_mapping';
        }

        return [
            'update_type' => $history ? 'history_message' : 'message_new',
            'text' => $text,
            'external_user_id' => $canonical_jid,
            'external_chat_id' => $canonical_jid,
            'external_message_id' => $external_message_id,
            'created' => $timestamp,
            'published_at' => $timestamp > 0 ? gmdate('Y-m-d H:i:s', $timestamp) : '',
            'display_name' => mb_substr(trim((string)preg_replace('/\s+/u', ' ', strip_tags($display_name))), 0, 191, 'UTF-8'),
            'username' => '',
            'profile_url' => (string)($resolved['avatar_url'] ?? ''),
            'avatar_url' => (string)($resolved['avatar_url'] ?? ''),
            'attachments' => $attachments,
            'direction' => $direction,
            'dedupe_key' => 'phpwa:' . (string)($payload['_easychat_event_id'] ?? $event['id'] ?? ''),
            'conversation_kind' => $group ? 'group' : 'dialog',
            'metadata' => $metadata,
        ];
    }

    public function sendMessage(string $token, string $external_user_id, string $text, array $attachments = [], array $options = []): array {
        $config = json_decode($token, true);
        $config = is_array($config) ? $config : [];
        $channel_id = (int)($config['channel_id'] ?? $options['channel_id'] ?? 0);
        $generation = (int)($config['generation'] ?? 0);
        $to = $this->jid((string)($options['external_chat_id'] ?? $external_user_id));
        $phone_to = $this->jid((string)($options['whatsapp_phone_jid'] ?? ''));
        if ($phone_to === '' && preg_match('/^[1-9][0-9]{6,19}$/D', trim((string)($options['whatsapp_phone_jid'] ?? ''))) === 1) {
            $phone_to = trim((string)$options['whatsapp_phone_jid']) . '@s.whatsapp.net';
        }
        $bound_account_hash = strtolower(trim((string)($options['whatsapp_account_hash'] ?? '')));
        if (preg_match('/^[a-f0-9]{64}$/D', $bound_account_hash) !== 1) {
            $bound_account_hash = '';
        }
        if (!$this->registry || $channel_id <= 0 || $generation <= 0 || $to === '') {
            return ['success' => false, 'error' => 'session_missing'];
        }
        $session_entered = false;
        $sent_messages = [];
        try {
            $lifecycle = new WhatsAppPersonalLifecycle($this->registry);
            $channel = (new \EasyChat\Model\Messenger($this->registry))->getChannelById($channel_id);
            if (!$channel || (string)($channel['status'] ?? '') !== 'active'
                || $lifecycle->generation($channel_id) !== $generation) {
                return ['success' => false, 'error' => 'session_replaced'];
            }
            $current_account = $this->jid((string)($channel['external_account_id'] ?? ''));
            $current_account_hash = $current_account !== '' ? hash('sha256', $current_account) : '';
            if ($bound_account_hash === '' || $current_account_hash === '' || !hash_equals($current_account_hash, $bound_account_hash)) {
                 
                 
                return ['success' => false, 'error' => 'contact_account_mismatch'];
            }
            $client = $lifecycle->client($channel, $generation);
            $send_session = function () use ($client, &$to, $text, $attachments, &$sent_messages, &$session_entered) {
                return $client->withOpenSession(function (Client $online) use ($to, $text, $attachments, &$sent_messages, &$session_entered) {
                    $session_entered = true;
                    return $this->sendInsideSession($online, $to, $text, $attachments, $sent_messages);
                }, 20.0, 500, 0.8);
            };
            $send_with_lock_wait = function () use (&$send_session) {
                $last_busy = null;
                 
                 
                 
                for ($attempt = 0; $attempt < 15; $attempt++) {
                    try {
                        return $send_session();
                    } catch (\PhpWA\Exception\AccountBusyException $e) {
                        $last_busy = $e;
                        if ($attempt === 14) { break; }
                        usleep(1000000);
                    }
                }
                throw $last_busy ?: new \PhpWA\Exception\AccountBusyException('WhatsApp account is busy.');
            };
            try {
                $sent = $send_with_lock_wait();
            } catch (\PhpWA\Exception\OutgoingMessageRejectedException $e) {
                if ($e->reason() !== '479') { throw $e; }
                 
                 
                 
                $client->resetSignalSessionsForTarget($to);
                $session_entered = false;
                try {
                    $sent = $send_with_lock_wait();
                } catch (\PhpWA\Exception\OutgoingMessageRejectedException $retry_error) {
                    if ($retry_error->reason() !== '479' || $phone_to === '' || $phone_to === $to) {
                        throw $retry_error;
                    }
                     
                     
                     
                     
                    $client->resetSignalSessionsForTarget($phone_to);
                    $to = $phone_to;
                    $session_entered = false;
                    $sent = $send_with_lock_wait();
                }
            }
            $message_ids = $this->sentMessageIds($sent_messages);
            return [
                'success' => true,
                'http_code' => 200,
                'external_message_id' => $sent ? $sent->messageId() : '',
                'external_message_ids' => $message_ids,
            ];
        } catch (\PhpWA\Exception\AccountBusyException $e) {
            return [
                'success' => false,
                'error' => 'provider_busy',
                'retryable' => true,
                'retry_without_attempt' => true,
                'retry_after_seconds' => 20,
            ];
        } catch (\PhpWA\Exception\OutgoingMessageRejectedException $e) {
            return [
                'success' => false,
                 
                 
                'error' => 'whatsapp_message_rejected',
                'retryable' => false,
                'outcome_unknown' => false,
                'partial_delivery' => false,
                'details' => 'whatsapp_ack_' . $e->reason(),
            ];
        } catch (\Throwable $e) {
            $message_ids = $this->sentMessageIds($sent_messages);
            return [
                'success' => false,
                'error' => $session_entered ? 'api_request_failed' : 'provider_temporarily_unavailable',
                 
                 
                 
                'retryable' => !$session_entered,
                'retry_after_seconds' => !$session_entered ? 30 : 0,
                'outcome_unknown' => $session_entered && $message_ids === [],
                'partial_delivery' => $message_ids !== [],
                'external_message_id' => $message_ids ? (string)end($message_ids) : '',
                'external_message_ids' => $message_ids,
                'details' => 'whatsapp_session_failed',
            ];
        }
    }

    private function sendInsideSession(Client $client, string $to, string $text, array $attachments, array &$sent_messages) {
        $last = null;
        $text_sent_as_caption = false;
        foreach ($attachments as $attachment) {
            $path = (string)($attachment['path'] ?? '');
            $name = (string)($attachment['file_name'] ?? $attachment['filename'] ?? basename($path));
            $mime = (string)($attachment['mime_type'] ?? $attachment['mime'] ?? '');
            if (!is_file($path) || !is_readable($path)) {
                throw new \RuntimeException('attachment_file_missing');
            }
            if ($mime === '' && function_exists('mime_content_type')) {
                $mime = (string)mime_content_type($path);
            }
            $caption = $last === null && $text !== '' ? mb_substr($text, 0, 4096, 'UTF-8') : null;
            if (strpos($mime, 'image/') === 0 && $mime !== 'image/svg+xml') {
                $last = $client->sendImage($to, $path, $mime, $caption);
                $text_sent_as_caption = $caption !== null;
            } elseif (strpos($mime, 'video/') === 0) {
                $last = $client->sendVideo($to, $path, $mime, $caption);
                $text_sent_as_caption = $caption !== null;
            } elseif (strpos($mime, 'audio/') === 0) {
                $last = $client->sendAudio($to, $path, $mime, false);
            } else {
                $validation = AttachmentPolicy::validateDocumentFile($path, $name, (int)filesize($path), 32);
                if (empty($validation['success'])) {
                    throw new \RuntimeException('attachment_type_unsupported');
                }
                $last = $client->sendDocument($to, $path, $mime ?: 'application/octet-stream', $name, $caption);
                $text_sent_as_caption = $caption !== null;
            }
            if ($last) $sent_messages[] = $last;
        }
        if ($last === null || ($text !== '' && !$text_sent_as_caption)) {
            $last = $client->sendText($to, mb_substr($text !== '' ? $text : ' ', 0, 4096, 'UTF-8'));
            if ($last) $sent_messages[] = $last;
        }
        return $last;
    }

    private function sentMessageIds(array $messages): array {
        return array_values(array_unique(array_filter(array_map(static function ($message): string {
            return is_object($message) && method_exists($message, 'messageId')
                ? (string)$message->messageId()
                : '';
        }, $messages))));
    }

    private function maskedLabel(int $channel_id, string $jid, WhatsAppPersonalIdentityRepository $identities, string $sender, string $alternate): string {
        $phone = $identities->confirmedPhone($channel_id, $sender, $alternate);
        if ($phone === '' && preg_match('/^([1-9][0-9]{5,19})@s\.whatsapp\.net$/D', $jid, $match)) {
            $phone = '+' . $match[1];
        }
        if ($phone === '') {
            return $this->isGroup($jid) ? 'Группа WhatsApp' : 'WhatsApp';
        }
        return 'WhatsApp •••• ' . substr($phone, -4);
    }

    private function unsupportedText(string $kind): string {
        $labels = [
            'reaction' => 'Реакция в WhatsApp', 'contact' => 'Контакт из WhatsApp',
            'contacts' => 'Контакты из WhatsApp', 'location' => 'Геопозиция из WhatsApp',
            'live_location' => 'Геопозиция в реальном времени', 'poll' => 'Опрос из WhatsApp',
            'sticker' => 'Стикер из WhatsApp', 'protocol' => '',
        ];
        return array_key_exists($kind, $labels) ? $labels[$kind] : 'Сообщение из WhatsApp';
    }

    private function jid(string $value): string {
        $value = strtolower(trim($value));
        if (preg_match('/^([a-z0-9_.-]+)(?::[0-9]+)?@(s\.whatsapp\.net|lid|g\.us|broadcast)$/D', $value, $match) !== 1) {
            return '';
        }
        return substr($match[1] . '@' . $match[2], 0, 191);
    }

    private function lidJid(string $value): string {
        $value = $this->jid($value);
        return substr($value, -4) === '@lid' ? $value : '';
    }

    private function isGroup(string $jid): bool {
        return substr($jid, -5) === '@g.us' || substr($jid, -10) === '@broadcast';
    }

    private function messageId(string $value): string {
        if ($value === '') {
            return '';
        }
        return preg_match('/^[A-Za-z0-9._:-]{1,256}$/D', $value) === 1 ? $value : hash('sha256', $value);
    }

    private function timestamp($value): int {
        if (is_int($value)) {
            return max(0, $value);
        }
        return is_string($value) && preg_match('/^(0|[1-9][0-9]{0,18})$/D', $value) === 1
            ? max(0, (int)$value)
            : 0;
    }
}
