<?php
namespace EasyChat\Library\Messenger;

use EasyChat\Library\AppClock;
use EasyChat\Library\CentralGatewayClient;
use EasyChat\Model\Messenger;
use EasyChat\Model\Setting;








final class MessageReadReceiptService {
    private const PROVIDER_WHATSAPP_PERSONAL = 'whatsapp-personal';
    public const CAPABILITY_UNSUPPORTED = 'unsupported';
    public const CAPABILITY_WIDGET_VISIBLE = 'widget_visible';
    public const CAPABILITY_MAX_DIALOG = 'max_dialog';
    public const CAPABILITY_WHATSAPP_DIALOG = 'whatsapp_dialog';

    private $registry;
    private $db;
    private $prefix;
    private $secret;

    public function __construct($registry) {
        $this->registry = $registry;
        $this->db = $registry->get('db');
        $this->prefix = easychat_db_prefix();
        $this->secret = CentralGatewayClient::ensureInstallSecret(new Setting($registry));
    }

    public static function capability(string $provider, string $conversation_kind = '', array $metadata = []): string {
        $provider = strtolower(trim($provider));
        if ($provider === '' || $provider === 'widget') {
            return self::CAPABILITY_WIDGET_VISIBLE;
        }
        $chat_type = strtoupper(trim((string)($metadata['chat_type'] ?? '')));
        if ($provider === 'max' && ($chat_type === 'DIALOG' || strtolower($conversation_kind) === 'dialog')) {
            return self::CAPABILITY_MAX_DIALOG;
        }
         
         
        if ($provider === self::PROVIDER_WHATSAPP_PERSONAL
            && ($chat_type === 'DIALOG' || strtolower($conversation_kind) === 'dialog')) {
            return self::CAPABILITY_WHATSAPP_DIALOG;
        }
        return self::CAPABILITY_UNSUPPORTED;
    }

    




    public function acknowledgeWidget(
        int $chat_id,
        int $through_message_id,
        string $session_id,
        string $reader_fingerprint = ''
    ): array {
        if ($chat_id <= 0 || $through_message_id <= 0 || $session_id === '') {
            return ['success' => false, 'error' => 'invalid_parameters'];
        }

        $session = $this->db->query(
            "SELECT `chat_id`, `session_id`, `source_provider`, `read_receipt_capability`
             FROM `{$this->prefix}sessions`
             WHERE `chat_id` = {$chat_id}
             LIMIT 1"
        )->row;
        if (!$session
            || !hash_equals((string)$session['session_id'], $session_id)
            || (string)$session['source_provider'] !== 'widget'
        ) {
            return ['success' => false, 'error' => 'session_not_found'];
        }

        $target = $this->db->query(
            "SELECT `message_id`
             FROM `{$this->prefix}messages`
             WHERE `chat_id` = {$chat_id}
               AND `message_id` = {$through_message_id}
               AND `author` IN (2, 3)
               AND `message_kind` NOT IN ('conversation_start_notice', 'messenger_system_event')
             LIMIT 1"
        )->row;
        if (!$target) {
            return ['success' => false, 'error' => 'message_not_acknowledgeable'];
        }

        $external_hash = $this->hmac('widget-chat|' . $chat_id . '|' . $session_id);
        $reader_hash = $this->hmac('widget-reader|' . $session_id . '|' . $reader_fingerprint);
        $observed_at = (new AppClock($this->registry))->nowSql();
        $this->storeCheckpoint(
            'widget',
            0,
            $chat_id,
            $external_hash,
            $reader_hash,
            'message_id',
            $through_message_id,
            $observed_at
        );

        $observed_e = $this->db->escape($observed_at);
        $this->db->query(
            "UPDATE `{$this->prefix}messages`
             SET `status_user` = 1,
                 `customer_read_at` = COALESCE(`customer_read_at`, '{$observed_e}'),
                 `customer_read_source` = IF(`customer_read_source` = '', 'widget_visible', `customer_read_source`),
                 `date_modified` = GREATEST(`date_modified`, '{$observed_e}')
             WHERE `chat_id` = {$chat_id}
               AND `message_id` <= {$through_message_id}
               AND `author` IN (2, 3)
               AND `message_kind` NOT IN ('conversation_start_notice', 'messenger_system_event')
               AND (`status_user` = 0 OR `customer_read_at` IS NULL)"
        );
        $changed = (int)$this->db->countAffected();
        if ($changed > 0) {
            $this->db->query(
                "UPDATE `{$this->prefix}sessions`
                 SET `read_receipt_capability` = 'widget_visible',
                     `date_modified` = GREATEST(`date_modified`, '{$observed_e}')
                 WHERE `chat_id` = {$chat_id}"
            );
        }
        return ['success' => true, 'changed' => $changed];
    }

    





    public function observeMax(
        int $channel_id,
        string $external_chat_id,
        string $reader_user_id,
        int $provider_cursor,
        string $observed_at = ''
    ): array {
        if ($channel_id <= 0 || $external_chat_id === '' || $reader_user_id === '' || $provider_cursor <= 0) {
            return ['success' => false, 'error' => 'invalid_parameters'];
        }

        $contact = (new Messenger($this->registry))->findContactByExternalChat($channel_id, $external_chat_id);
        $chat_id = (int)($contact['chat_id'] ?? 0);
        $metadata = json_decode((string)($contact['metadata_json'] ?? ''), true);
        if (!is_array($metadata)) {
            $metadata = [];
        }
        if ($chat_id <= 0 || strtoupper((string)($metadata['chat_type'] ?? '')) !== 'DIALOG') {
            return ['success' => true, 'changed' => 0, 'ignored' => true];
        }

        $observed_at = $this->normalizeDate($observed_at);
        if ($observed_at === '') {
            $observed_at = (new AppClock($this->registry))->nowSql();
        }
        $external_hash = $this->hmac('max-chat|' . $channel_id . '|' . $external_chat_id);
        $reader_hash = $this->hmac('max-reader|' . $channel_id . '|' . $reader_user_id);
        $this->storeCheckpoint(
            'max',
            $channel_id,
            $chat_id,
            $external_hash,
            $reader_hash,
            'provider_time_ms',
            $provider_cursor,
            $observed_at
        );

        $observed_e = $this->db->escape($observed_at);
        $this->db->query(
            "UPDATE `{$this->prefix}messages` m
             INNER JOIN `{$this->prefix}messenger_deliveries` d
                ON d.`message_id` = m.`message_id`
               AND d.`channel_id` = {$channel_id}
               AND d.`direction` = 'out'
             SET m.`status_user` = 1,
                 m.`customer_read_at` = COALESCE(m.`customer_read_at`, '{$observed_e}'),
                 m.`customer_read_source` = IF(m.`customer_read_source` = '', 'max_dialog', m.`customer_read_source`),
                 m.`date_modified` = GREATEST(m.`date_modified`, '{$observed_e}')
             WHERE m.`chat_id` = {$chat_id}
               AND m.`author` IN (2, 3)
               AND d.`provider_cursor` IS NOT NULL
               AND d.`provider_cursor` <= {$provider_cursor}
               AND (m.`status_user` = 0 OR m.`customer_read_at` IS NULL)"
        );
        $changed = (int)$this->db->countAffected();
        $this->db->query(
            "UPDATE `{$this->prefix}sessions`
             SET `read_receipt_capability` = 'max_dialog',
                 `date_modified` = IF({$changed} > 0, GREATEST(`date_modified`, '{$observed_e}'), `date_modified`)
             WHERE `chat_id` = {$chat_id}"
        );
        return ['success' => true, 'changed' => $changed];
    }

     
    public function payload(array $message, string $capability): ?array {
        if ($capability === self::CAPABILITY_UNSUPPORTED || (int)($message['author'] ?? 1) === 1) {
            return null;
        }
        return [
            'state' => (int)($message['status_user'] ?? 0) === 1 ? 'read' : 'sent',
            'read_at' => !empty($message['customer_read_at']) ? (string)$message['customer_read_at'] : null,
            'source' => (string)($message['customer_read_source'] ?? ''),
        ];
    }

    



    public function projectMaxMessage(
        int $channel_id,
        string $external_chat_id,
        int $message_id,
        int $provider_cursor
    ): bool {
        if ($channel_id <= 0 || $external_chat_id === '' || $message_id <= 0 || $provider_cursor <= 0) {
            return false;
        }
        $external_hash = $this->hmac('max-chat|' . $channel_id . '|' . $external_chat_id);
        $checkpoint = $this->db->query(
            "SELECT `observed_at`
             FROM `{$this->prefix}message_read_checkpoints`
             WHERE `provider` = 'max'
               AND `channel_id` = {$channel_id}
               AND `external_chat_hash` = '" . $this->db->escape($external_hash) . "'
               AND `cursor_value` >= {$provider_cursor}
             ORDER BY `cursor_value` DESC
             LIMIT 1"
        )->row;
        if (!$checkpoint) {
            return false;
        }
        $observed = $this->normalizeDate((string)($checkpoint['observed_at'] ?? ''));
        if ($observed === '') {
            $observed = (new AppClock($this->registry))->nowSql();
        }
        $this->db->query(
            "UPDATE `{$this->prefix}messages`
             SET `status_user` = 1,
                 `customer_read_at` = COALESCE(`customer_read_at`, '" . $this->db->escape($observed) . "'),
                 `customer_read_source` = IF(`customer_read_source` = '', 'max_dialog', `customer_read_source`),
                 `date_modified` = GREATEST(`date_modified`, '" . $this->db->escape($observed) . "')
             WHERE `message_id` = {$message_id}
               AND `author` IN (2, 3)
               AND (`status_user` = 0 OR `customer_read_at` IS NULL)"
        );
        return (int)$this->db->countAffected() > 0;
    }

    




    public function observeWhatsApp(
        int $channel_id,
        string $external_chat_id,
        string $reader_jid,
        array $external_message_ids,
        int $timestamp = 0
    ): array {
        if ($channel_id <= 0 || $external_chat_id === '' || $reader_jid === '') {
            return ['success' => false, 'error' => 'invalid_parameters'];
        }
        $ids = [];
        foreach ($external_message_ids as $id) {
            $id = trim((string)$id);
            if (preg_match('/^[A-Za-z0-9._:-]{1,256}$/D', $id) === 1) {
                $ids[$id] = $id;
            }
            if (count($ids) >= 1000) break;
        }
        if (!$ids) {
            return ['success' => false, 'error' => 'message_ids_missing'];
        }
        $contact = (new Messenger($this->registry))->findContactByExternalChat($channel_id, $external_chat_id);
        if (!$contact) {
            $contact = (new Messenger($this->registry))->findContact($channel_id, $external_chat_id);
        }
        $chat_id = (int)($contact['chat_id'] ?? 0);
        $metadata = json_decode((string)($contact['metadata_json'] ?? '{}'), true);
        $metadata = is_array($metadata) ? $metadata : [];
        if ($chat_id <= 0 || strtoupper((string)($metadata['chat_type'] ?? '')) !== 'DIALOG') {
             
             
             
             
            $quoted_ids = array_map(function (string $id): string {
                return "'" . $this->db->escape($id) . "'";
            }, array_values($ids));
            $fallback = $this->db->query(
                "SELECT DISTINCT m.`chat_id`
                 FROM `{$this->prefix}messages` m
                 INNER JOIN `{$this->prefix}messenger_deliveries` d
                    ON d.`message_id` = m.`message_id`
                   AND d.`channel_id` = {$channel_id}
                   AND d.`direction` = 'out'
                 WHERE d.`external_message_id` IN (" . implode(',', $quoted_ids) . ")
                   AND m.`author` IN (2, 3)
                 LIMIT 2"
            );
            $fallback_chat_ids = array_values(array_unique(array_map(static function (array $row): int {
                return (int)($row['chat_id'] ?? 0);
            }, is_array($fallback->rows ?? null) ? $fallback->rows : [])));
            if (count($fallback_chat_ids) === 1 && $fallback_chat_ids[0] > 0) {
                $chat_id = $fallback_chat_ids[0];
                $fallback_contact = $this->db->query(
                    "SELECT `metadata_json`
                     FROM `{$this->prefix}messenger_contacts`
                     WHERE `channel_id` = {$channel_id}
                       AND `chat_id` = {$chat_id}
                     LIMIT 1"
                )->row;
                $metadata = json_decode((string)($fallback_contact['metadata_json'] ?? '{}'), true);
                $metadata = is_array($metadata) ? $metadata : [];
            }
        }
        if ($chat_id <= 0 || strtoupper((string)($metadata['chat_type'] ?? '')) !== 'DIALOG') {
            return ['success' => true, 'changed' => 0, 'ignored' => true];
        }
        $clock = new AppClock($this->registry);
        $timestamp = $timestamp > 0 && $timestamp <= time() + 300 ? $timestamp : 0;
        $observed_at = $timestamp > 0 ? $clock->fromUnixTimestampSql($timestamp) : $clock->nowSql();
        $reader_hash = $this->hmac('whatsapp-reader|' . $channel_id . '|' . $reader_jid);
        $this->storeWhatsAppCheckpoints(
            $channel_id,
            $chat_id,
            array_values($ids),
            $reader_hash,
            max(1, $timestamp),
            $observed_at
        );
        $observed_e = $this->db->escape($observed_at);
         
         
         
        $modified_e = $this->db->escape((new AppClock($this->registry))->nowSql());
        $changed = 0;
        foreach (array_chunk(array_values($ids), 200) as $chunk) {
            $quoted = array_map(function ($id) {
                return "'" . $this->db->escape($id) . "'";
            }, $chunk);
            $this->db->query(
                "UPDATE `{$this->prefix}messages` m
                 INNER JOIN `{$this->prefix}messenger_deliveries` d
                    ON d.`message_id` = m.`message_id`
                   AND d.`channel_id` = {$channel_id}
                   AND d.`direction` = 'out'
                 SET m.`status_user` = 1,
                     m.`customer_read_at` = COALESCE(m.`customer_read_at`, '{$observed_e}'),
                     m.`customer_read_source` = IF(m.`customer_read_source` = '', 'whatsapp_dialog', m.`customer_read_source`),
                     m.`date_modified` = GREATEST(m.`date_modified`, '{$observed_e}', '{$modified_e}')
                 WHERE m.`chat_id` = {$chat_id}
                   AND d.`external_message_id` IN (" . implode(',', $quoted) . ")
                   AND m.`author` IN (2, 3)
                   AND (m.`status_user` = 0 OR m.`customer_read_at` IS NULL)"
            );
            $changed += max(0, (int)$this->db->countAffected());
        }
        $this->db->query(
            "UPDATE `{$this->prefix}sessions`
             SET `read_receipt_capability` = 'whatsapp_dialog',
                 `date_modified` = IF({$changed} > 0, GREATEST(`date_modified`, '{$observed_e}', '{$modified_e}'), `date_modified`)
             WHERE `chat_id` = {$chat_id}"
        );
        return ['success' => true, 'changed' => $changed];
    }

    public function projectWhatsAppMessage(int $channel_id, string $external_message_id, int $message_id): bool {
        if ($channel_id <= 0 || $message_id <= 0 || preg_match('/^[A-Za-z0-9._:-]{1,256}$/D', $external_message_id) !== 1) {
            return false;
        }
        $hash = $this->hmac('whatsapp-message|' . $channel_id . '|' . $external_message_id);
        $checkpoint = $this->db->query(
            "SELECT `chat_id`, `observed_at` FROM `{$this->prefix}message_read_checkpoints`
             WHERE `provider` = 'whatsapp-personal'
               AND `channel_id` = {$channel_id}
               AND `external_chat_hash` = '" . $this->db->escape($hash) . "'
             ORDER BY `observed_at` DESC LIMIT 1"
        )->row;
        if (!$checkpoint) return false;
        $checkpoint_chat_id = max(0, (int)($checkpoint['chat_id'] ?? 0));
        $observed = $this->normalizeDate((string)$checkpoint['observed_at']);
        if ($observed === '') $observed = (new AppClock($this->registry))->nowSql();
        $modified_e = $this->db->escape((new AppClock($this->registry))->nowSql());
        $this->db->query(
            "UPDATE `{$this->prefix}messages`
             SET `status_user` = 1,
                 `customer_read_at` = COALESCE(`customer_read_at`, '" . $this->db->escape($observed) . "'),
                 `customer_read_source` = IF(`customer_read_source` = '', 'whatsapp_dialog', `customer_read_source`),
                 `date_modified` = GREATEST(`date_modified`, '" . $this->db->escape($observed) . "', '{$modified_e}')
             WHERE `message_id` = {$message_id}
               AND `chat_id` = {$checkpoint_chat_id}
               AND `author` IN (2, 3)"
        );
        return (int)$this->db->countAffected() > 0;
    }

    private function storeCheckpoint(
        string $provider,
        int $channel_id,
        int $chat_id,
        string $external_hash,
        string $reader_hash,
        string $cursor_type,
        int $cursor_value,
        string $observed_at
    ): void {
        $this->db->query(
            "INSERT INTO `{$this->prefix}message_read_checkpoints`
                (`provider`, `channel_id`, `chat_id`, `external_chat_hash`, `reader_hash`,
                 `cursor_type`, `cursor_value`, `observed_at`, `date_modified`)
             VALUES
                ('" . $this->db->escape($provider) . "', {$channel_id}, {$chat_id},
                 '" . $this->db->escape($external_hash) . "', '" . $this->db->escape($reader_hash) . "',
                 '" . $this->db->escape($cursor_type) . "', {$cursor_value},
                 '" . $this->db->escape($observed_at) . "', NOW())
             ON DUPLICATE KEY UPDATE
                 `chat_id` = VALUES(`chat_id`),
                 `cursor_value` = GREATEST(`cursor_value`, VALUES(`cursor_value`)),
                 `observed_at` = IF(VALUES(`cursor_value`) >= `cursor_value`, VALUES(`observed_at`), `observed_at`),
                 `date_modified` = NOW()"
        );
    }

    




    private function storeWhatsAppCheckpoints(
        int $channel_id,
        int $chat_id,
        array $message_ids,
        string $reader_hash,
        int $cursor_value,
        string $observed_at
    ): void {
        $reader_e = $this->db->escape($reader_hash);
        $observed_e = $this->db->escape($observed_at);
        foreach (array_chunk($message_ids, 200) as $chunk) {
            $values = [];
            foreach ($chunk as $external_message_id) {
                $message_hash = $this->hmac('whatsapp-message|' . $channel_id . '|' . (string)$external_message_id);
                $values[] = "('whatsapp-personal', {$channel_id}, {$chat_id}, '"
                    . $this->db->escape($message_hash) . "', '{$reader_e}', 'external_id', {$cursor_value}, '{$observed_e}', NOW())";
            }
            if (!$values) {
                continue;
            }
            $this->db->query(
                "INSERT INTO `{$this->prefix}message_read_checkpoints`
                    (`provider`, `channel_id`, `chat_id`, `external_chat_hash`, `reader_hash`,
                     `cursor_type`, `cursor_value`, `observed_at`, `date_modified`)
                 VALUES " . implode(',', $values) . "
                 ON DUPLICATE KEY UPDATE
                    `chat_id` = VALUES(`chat_id`),
                    `cursor_value` = GREATEST(`cursor_value`, VALUES(`cursor_value`)),
                    `observed_at` = IF(VALUES(`cursor_value`) >= `cursor_value`, VALUES(`observed_at`), `observed_at`),
                    `date_modified` = NOW()"
            );
        }
    }

    private function hmac(string $value): string {
        return hash_hmac('sha256', $value, $this->secret);
    }

    private function normalizeDate(string $value): string {
        $value = trim($value);
        return preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/D', $value) === 1 ? $value : '';
    }
}
