<?php
namespace EasyChat\Library\Messenger;

use EasyChat\Model\Messenger as MessengerModel;
use EasyChat\Model\SmsDevice;
use EasyChat\Library\Notification\TrialLockGuard;
use EasyChat\Library\Queue\Jobs\SmsInboundPushJob;
use EasyChat\Library\Queue\QueueManager;

class SmsInboundService {
    private $registry;
    private $db;
    private $logger;

    public function __construct($registry) {
        $this->registry = $registry;
        $this->db = $registry->get('db');
        $this->logger = new MessengerLogger($registry, SmsProvider::PROVIDER);
    }

    public function receiveFromDevice(array $device, array $payload): array {
        if ((new TrialLockGuard($this->registry))->isSmsBlocked(false)) {
            $this->logger->warning('sms_inbound_rejected_trial_limit', [
                'device_id' => (int)($device['device_id'] ?? 0),
            ]);
            return ['success' => false, 'error' => 'trial_limit_exceeded', 'code' => 'trial_limit_exceeded'];
        }

        if (!$this->deviceCanReceive($device)) {
            $this->logger->warning('sms_inbound_rejected_permission_missing', [
                'device_id' => (int)($device['device_id'] ?? 0),
                'admin_requested' => (int)($device['inbound_admin_requested'] ?? 0),
                'operator_consent' => (string)($device['inbound_operator_consent'] ?? ''),
                'android_permission' => (string)($device['inbound_android_permission'] ?? ''),
                'status' => (string)($device['inbound_status'] ?? ''),
            ]);
            return ['success' => false, 'error' => 'sms_inbound_permission_missing'];
        }

        $phone = SmsPhoneNumber::normalize((string)($payload['phone'] ?? ''));
        $text = trim((string)($payload['text'] ?? ''));
        if ($phone === '' || $text === '') {
            $this->logger->warning('sms_inbound_rejected_bad_payload', [
                'device_id' => (int)($device['device_id'] ?? 0),
                'text_length' => strlen($text),
            ]);
            return ['success' => false, 'error' => 'sms_inbound_bad_payload'];
        }

        $channel = (new SmsOutboundService($this->registry))->ensureChannel();
        if (!$channel) {
            $this->logger->warning('sms_inbound_rejected_channel_missing', [
                'device_id' => (int)($device['device_id'] ?? 0),
            ]);
            return ['success' => false, 'error' => 'sms_channel_missing'];
        }

        $model_messenger = new MessengerModel($this->registry);
        $contact = $model_messenger->findContactByExternalUserIds(
            (int)$channel['channel_id'],
            SmsPhoneNumber::lookupVariants($phone)
        );
        if (!$contact || (int)($contact['chat_id'] ?? 0) <= 0) {
            $this->logger->info('sms_inbound_ignored_unknown_phone', [
                'device_id' => (int)($device['device_id'] ?? 0),
            ]);
            return ['success' => true, 'ignored' => 'unknown_phone'];
        }

        $dedupe_key = $this->dedupeKey($device, $phone, $text, $payload);
        $subscription_id = max(-1, min(2147483647, (int)($payload['sim_subscription_id'] ?? -1)));
        $device_message_id = preg_replace('/[^a-zA-Z0-9:_-]+/', '', (string)($payload['device_message_id'] ?? ''));
        $device_message_id = substr((string)$device_message_id, 0, 128);
        $contact_external_user_id = trim((string)($contact['external_user_id'] ?? ''));
        if ($contact_external_user_id === '') {
            $contact_external_user_id = $phone;
        }
        $contact_external_chat_id = trim((string)($contact['external_chat_id'] ?? ''));
        if ($contact_external_chat_id === '') {
            $contact_external_chat_id = $contact_external_user_id;
        }
        $event = [
            'update_type' => 'message_created',
            'direction' => 'in',
             
             
             
            'external_user_id' => $contact_external_user_id,
            'external_chat_id' => $contact_external_chat_id,
            'external_message_id' => 'sms-in:' . (int)$device['device_id'] . ':' . $dedupe_key,
            'dedupe_key' => $dedupe_key,
            'text' => $text,
            'created' => $this->receivedAtTimestamp((string)($payload['received_at'] ?? '')),
            'display_name' => (string)($contact['display_name'] ?? $phone),
            'metadata' => [
                'phone' => $phone,
                'source' => 'android_sms_receiver',
                'device_id' => (string)(int)($device['device_id'] ?? 0),
                'sim_subscription_id' => (string)$subscription_id,
                'device_message_id' => $device_message_id,
            ],
        ];

         
         
         
        $lock_name = 'ec_sms_in_' . substr(hash('sha256', (int)$channel['channel_id'] . '|' . $dedupe_key), 0, 32);
        $lock_acquired = false;
        $transaction_open = false;
        $notification_jobs = [];
         
         
        $notification_queue = new QueueManager($this->registry);
        $result = [];
        try {
            $lock = $this->db->query(
                "SELECT GET_LOCK('" . $this->db->escape($lock_name) . "', 3) AS `locked`"
            );
            $lock_acquired = $lock && (int)($lock->row['locked'] ?? 0) === 1;
            if (!$lock_acquired) {
                return ['success' => false, 'error' => 'sms_inbound_busy', 'retryable' => true];
            }

            $this->db->query('START TRANSACTION');
            $transaction_open = true;
            $result = (new InboundService($this->registry))->handleWebhook(
                SmsProvider::PROVIDER,
                $channel,
                $event,
                json_encode($event, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
                '',
                true
            );
            if (empty($result['success'])) {
                $this->db->query('ROLLBACK');
                $transaction_open = false;
                return $result;
            }
            if (empty($result['duplicate'])) {
                (new SmsDevice($this->registry))->markInboundUsed((int)$device['device_id'], $subscription_id);
            }
            if (empty($result['duplicate']) && empty($result['ignored'])) {
                $chat_id = (int)($result['chat_id'] ?? 0);
                $message_id = (int)($result['message_id'] ?? 0);
                if ($chat_id <= 0 || $message_id <= 0) {
                    throw new \RuntimeException('sms_inbound_identity_missing');
                }
                 
                 
                foreach (['fcm', 'webpush'] as $push_channel) {
                    $notification_job_id = $notification_queue->pushUnique(
                        'sms_inbound_push',
                        ['chat_id' => $chat_id, 'message_id' => $message_id, 'channel' => $push_channel],
                        'sms_inbound_' . $push_channel . '_push:' . $message_id,
                        0,
                        100
                    );
                    if ($notification_job_id <= 0) {
                        throw new \RuntimeException('sms_inbound_push_enqueue_failed');
                    }
                    $notification_jobs[$push_channel] = $notification_job_id;
                }
            }
            $this->db->query('COMMIT');
            $transaction_open = false;
        } catch (\Throwable $e) {
            if ($transaction_open) {
                try { $this->db->query('ROLLBACK'); } catch (\Throwable $ignored) {}
            }
            return ['success' => false, 'error' => 'sms_inbound_storage_failed', 'retryable' => true];
        } finally {
            if ($lock_acquired) {
                try {
                    $this->db->query("SELECT RELEASE_LOCK('" . $this->db->escape($lock_name) . "')");
                } catch (\Throwable $ignored) {
                }
            }
        }

         
         
         
        foreach ($notification_jobs as $push_channel => $notification_job_id) {
            $claimed = $notification_queue->claimById($notification_job_id);
            if (!$claimed) continue;
            $lock_token = (string)($claimed['lock_token'] ?? '');
            try {
                (new SmsInboundPushJob($this->registry))->handle([
                    'chat_id' => (int)($result['chat_id'] ?? 0),
                    'message_id' => (int)($result['message_id'] ?? 0),
                    'channel' => $push_channel,
                ]);
                $notification_queue->markDone($notification_job_id, $lock_token);
            } catch (\Throwable $e) {
                $notification_queue->reschedule(
                    $notification_job_id,
                    15,
                    'sms_inbound_push_retry',
                    $lock_token
                );
                $this->logger->warning('sms_inbound_push_failed', [
                    'device_id' => (int)($device['device_id'] ?? 0),
                    'chat_id' => (int)($result['chat_id'] ?? 0),
                    'queue_job_id' => $notification_job_id,
                    'push_channel' => $push_channel,
                ]);
            }
        }

        $this->logger->info('sms_inbound_processed', [
            'device_id' => (int)($device['device_id'] ?? 0),
            'chat_id' => (int)($result['chat_id'] ?? 0),
            'message_id' => (int)($result['message_id'] ?? 0),
            'duplicate' => !empty($result['duplicate']) ? 1 : 0,
        ]);

        return $result + ['success' => true];
    }

    private function deviceCanReceive(array $device): bool {
        return !empty($device)
            && (int)($device['inbound_admin_requested'] ?? 0) === 1
            && (string)($device['inbound_operator_consent'] ?? '') === 'granted'
            && (string)($device['inbound_android_permission'] ?? '') === 'granted'
            && (string)($device['inbound_status'] ?? '') === 'ready';
    }

    private function dedupeKey(array $device, string $phone, string $text, array $payload): string {
        $client_key = preg_replace('/[^a-zA-Z0-9:_-]+/', '', (string)($payload['dedupe_key'] ?? ''));
        if ($client_key !== '') {
            return substr($client_key, 0, 120);
        }
        return hash('sha256', implode('|', [
            (int)($device['device_id'] ?? 0),
            $phone,
            (string)($payload['received_at'] ?? ''),
            $text,
        ]));
    }

    private function receivedAtTimestamp(string $value, ?int $server_now = null): int {
        $server_now = $server_now ?? time();
        $value = trim($value);
        if ($value === '') {
            return $server_now;
        }
        if (preg_match('/^\d+$/', $value)) {
            $timestamp = (int)$value;
            $timestamp = $timestamp > 100000000000 ? (int)floor($timestamp / 1000) : $timestamp;
        } else {
            $parsed = strtotime($value);
            $timestamp = $parsed ? (int)$parsed : 0;
        }
         
         
        if ($timestamp < $server_now - 30 * 86400 || $timestamp > $server_now + 5 * 60) {
            return $server_now;
        }
        return $timestamp;
    }
}
