<?php
namespace EasyChat\Library;

use EasyChat\Model\AppSession;
use EasyChat\Model\AppDevice;
use EasyChat\Model\Setting;
use EasyChat\Model\SmsDevice;
use EasyChat\Model\ChatAssignment;
use EasyChat\Model\Manager;
use EasyChat\Library\Messenger\SmsInstallationScope;








class AndroidPushNotifier {
    private $registry;
    private $settings;
    private $logger;

    public function __construct($registry) {
        $this->registry = $registry;
        $this->settings = new Setting($registry);
        $this->logger = $this->settings->get('chat_fcm_logging', '0') === '1'
            ? new Log('fcm')
            : new class { public function write($message) {} };
    }

    public function notifyNewMessage(int $chat_id, string $source_provider, string $customer_name = '', string $source_label = '', array $channels = []): array {
        $event = [
            'type' => 'new_message',
            'chat_id' => $chat_id,
            'source_provider' => $source_provider,
            'source_label' => $source_label,
            'customer_name' => $customer_name,
        ];
        $active_manager_ids = (new ChatAssignment($this->registry))->getActiveManagerIds($chat_id);
        if (!$active_manager_ids) {
             
             
             
            foreach ((new Manager($this->registry))->getActiveManagers() as $manager) {
                $manager_id = (int)($manager['id'] ?? 0);
                if ($manager_id > 0) {
                    $active_manager_ids[] = $manager_id;
                }
            }
        }
        if (!$active_manager_ids) {
            return ['success' => true, 'sent' => 0, 'skipped' => 'no_active_managers'];
        }
        if ($active_manager_ids) {
            $install_id = CentralGatewayClient::ensureInstallId($this->settings);
            $event['operator_ref_hashes'] = array_map(
                static function ($manager_id) use ($install_id) {
                    return hash('sha256', $install_id . ':' . (int)$manager_id);
                },
                $active_manager_ids
            );
        }
        return $this->notify($event, $channels);
    }

    public function notifySyncChat(int $chat_id, string $source_provider, string $customer_name = '', string $source_label = ''): array {
        $operator_ref_hashes = $this->activeOperatorRefHashes();
        if (!$operator_ref_hashes) {
            return ['success' => true, 'sent' => 0, 'skipped' => 'no_active_managers'];
        }
        return $this->notify([
            'type' => 'sync_chat',
            'chat_id' => $chat_id,
            'source_provider' => $source_provider,
            'source_label' => $source_label,
            'customer_name' => $customer_name,
            'operator_ref_hashes' => $operator_ref_hashes,
        ]);
    }

    






    public function notifyChatAssignment(int $chat_id, int $manager_id, bool $assigned, array $channels = []): array {
        if ($chat_id <= 0 || $manager_id <= 0) {
            return ['success' => false, 'sent' => 0, 'failed' => 1, 'error' => 'chat_assignment_push_invalid'];
        }
        $manager = (new Manager($this->registry))->getManagerById($manager_id);
        if (!$manager || (int)($manager['status'] ?? 0) !== 1) {
            return ['success' => true, 'sent' => 0, 'skipped' => 'inactive_manager'];
        }

        $install_id = CentralGatewayClient::ensureInstallId($this->settings);
        $operator_ref_hash = hash('sha256', $install_id . ':' . $manager_id);
        return $this->notify([
            'type' => $assigned ? 'chat_assigned' : 'chat_assignment_changed',
            'chat_id' => $chat_id,
            'source_provider' => 'system',
            'source_label' => 'EasyChat',
            'preview' => $assigned ? 'Вам назначен диалог' : '',
            'operator_ref_hashes' => [$operator_ref_hash],
        ], $channels);
    }

    public function notifySmsPermissionRequest(int $app_session_id): array {
        return $this->notifyTokenSession($app_session_id, [
            'type' => 'sms_permission_requested',
            'chat_id' => 0,
            'source_provider' => 'sms',
            'source_label' => 'SMS',
            'preview' => 'Администратор запросил разрешение на отправку SMS',
        ]);
    }

    public function notifySmsInboundPermissionRequest(int $app_session_id): array {
        return $this->notifyTokenSession($app_session_id, [
            'type' => 'sms_inbound_permission_requested',
            'chat_id' => 0,
            'source_provider' => 'sms',
            'source_label' => 'SMS',
            'preview' => 'Администратор запросил разрешение на прием SMS-ответов',
        ]);
    }

    public function notifySmsInboundPermissionRevoked(int $app_session_id): array {
        return $this->notifyTokenSession($app_session_id, [
            'type' => 'sms_inbound_permission_revoked',
            'chat_id' => 0,
            'source_provider' => 'sms',
            'source_label' => 'SMS',
            'preview' => 'Прием SMS-ответов отключен администратором',
        ]);
    }

    public function notifySmsInboundFilterChanged(int $app_session_id): array {
        return $this->notifyTokenSession($app_session_id, [
            'type' => 'sms_inbound_filter_changed',
            'chat_id' => 0,
            'source_provider' => 'sms',
            'source_label' => 'SMS',
            'preview' => 'Список SMS-диалогов обновлен',
        ]);
    }

    public function notifySmsInboundFilterChangedForReadyDevices(): array {
        $sent = 0;
        $failed = 0;
        try {
            $session_ids = (new SmsDevice($this->registry))->listInboundReadySessionIds();
            foreach ($session_ids as $session_id) {
                try {
                    $result = $this->notifySmsInboundFilterChanged((int)$session_id);
                    $sent += (int)($result['sent'] ?? 0);
                    $failed += (int)($result['failed'] ?? 0);
                } catch (\Throwable $e) {
                    $failed++;
                }
            }
        } catch (\Throwable $e) {
            $failed++;
        }
        return ['success' => $failed === 0, 'sent' => $sent, 'failed' => $failed];
    }

    public function notifySmsJob(int $app_session_id, int $job_id): array {
        return $this->notifyTokenSession($app_session_id, [
            'type' => 'sms_job_available',
            'chat_id' => 0,
            'job_id' => (string)$job_id,
            'source_provider' => 'sms',
            'source_label' => 'SMS',
            'preview' => 'Есть SMS-задача',
        ]);
    }

    private function notifyTokenSession(int $app_session_id, array $event): array {
        $app_sessions = new AppSession($this->registry);
        $session = $app_sessions->getSessionById($app_session_id);
        if (!$session
            || !empty($session['revoked_at'])
            || (!empty($session['refresh_token_hash'])
                && strtotime((string)$session['refresh_expires_at']) <= time())
        ) {
            return ['success' => true, 'sent' => 0, 'skipped' => 'inactive_session'];
        }
        $token = (string)($session['fcm_token'] ?? '');
        if ($token === '') {
            return ['success' => true, 'sent' => 0, 'skipped' => 'no_target_token'];
        }
        $scope_credential = !empty($session['app_device_id'])
            ? (new AppDevice($this->registry))->scopeCredentialById((int)$session['app_device_id'])
            : (string)($session['session_token'] ?? '');
        $installation_scope = SmsInstallationScope::create(
            SmsInstallationScope::currentSiteUrl(),
            $scope_credential
        );
        if ($installation_scope === '') {
            return ['success' => false, 'sent' => 0, 'failed' => 1, 'error' => 'installation_scope_invalid'];
        }

        $central_api = CentralGatewayClient::normalizeBaseUrl($this->settings->get('chat_central_api'));
        if ($central_api === '') {
            return ['success' => true, 'sent' => 0, 'skipped' => 'central_api_empty'];
        }

        $license = trim((string)$this->settings->get('chat_license'));
        $install_id = CentralGatewayClient::ensureInstallId($this->settings);
        $install_secret = $license === ''
            ? CentralGatewayClient::ensureInstallSecret($this->settings)
            : '';
        $data = $this->buildDataPayload($event);
        $data['installation_scope'] = $installation_scope;
        if (isset($event['job_id'])) {
            $data['job_id'] = (string)$event['job_id'];
        }
        foreach (['code'] as $key) {
            if (!empty($event[$key])) $data[$key] = (string)$event[$key];
        }
        $delivery = $this->sendToken($central_api, $license, $install_id, $install_secret, $token, $data);
        if ($delivery['status'] === 'delivered') {
            $app_sessions->markPushDeliveredByFcmToken($token);
            return ['success' => true, 'sent' => 1, 'failed' => 0];
        }
        if ($delivery['status'] === 'pruned') {
            $app_sessions->deleteSessionsByFcmToken($token);
            return ['success' => false, 'sent' => 0, 'failed' => 1, 'pruned' => 1, 'error' => 'push_token_not_registered'];
        }
        $app_sessions->markPushErrorByFcmToken($token, (string)($delivery['error_code'] ?? 'push_delivery_failed'));
        return ['success' => false, 'sent' => 0, 'failed' => 1, 'error' => (string)($delivery['error_code'] ?? 'push_delivery_failed')];
    }

    private function notify(array $event, array $requested_channels = []): array {
        $event_type = (string)($event['type'] ?? 'new_message');
        $this->logger->write('=== AndroidPushNotifier trigger ===');
        $this->logger->write('event_type=' . $event_type . ', chat_id=' . (int)($event['chat_id'] ?? 0));

        $app_sessions = new AppSession($this->registry);
        $central_api = CentralGatewayClient::normalizeBaseUrl($this->settings->get('chat_central_api'));
        if ($central_api === '') {
            $this->logger->write('push skipped: central api URL is empty or invalid');
            return ['success' => true, 'sent' => 0, 'skipped' => 'central_api_empty'];
        }

        $license = trim((string)$this->settings->get('chat_license'));
        $install_id = CentralGatewayClient::ensureInstallId($this->settings);
        $install_secret = $license === ''
            ? CentralGatewayClient::ensureInstallSecret($this->settings)
            : '';

        $sent = 0;
        $failed = 0;
        $pruned = 0;
        $data = $this->buildDataPayload($event);
        $channels = $this->requestedChannels($event_type, $requested_channels);
        $operator_ref_hashes = is_array($event['operator_ref_hashes'] ?? null)
            ? $event['operator_ref_hashes']
            : [];
        $fanout = $this->sendFanout(
            $central_api,
            $license,
            $install_id,
            $install_secret,
            $data,
            $channels,
            $operator_ref_hashes
        );
        if (!empty($fanout['supported'])) {
            $fanout_payload = is_array($fanout['fanout'] ?? null) ? $fanout['fanout'] : [];
            $channel_stats = is_array($fanout_payload['channels'] ?? null) ? $fanout_payload['channels'] : [];
            $fcm_stats = is_array($channel_stats['fcm'] ?? null) ? $channel_stats['fcm'] : [];
            $fcm_delivered = (int)($fcm_stats['delivered'] ?? 0);
            $fcm_failed = (int)($fcm_stats['failed'] ?? 0);
            $webpush_stats = is_array($channel_stats['webpush'] ?? null) ? $channel_stats['webpush'] : [];
            $webpush_delivered = (int)($webpush_stats['delivered'] ?? 0);
            $webpush_failed = (int)($webpush_stats['failed'] ?? 0);
            $local_tokens = $app_sessions->getAllFcmTokens();
            if ($fcm_delivered > 0) {
                foreach ($local_tokens as $token) {
                    $app_sessions->markPushDeliveredByFcmToken((string)$token);
                }
            } elseif ($fcm_failed > 0) {
                foreach ($local_tokens as $token) {
                    $app_sessions->markPushErrorByFcmToken((string)$token, 'fanout_fcm_failed');
                }
            }

            $delivered = (int)($fanout_payload['delivered'] ?? 0);
            $failed_count = (int)($fanout_payload['failed'] ?? 0);
            return [
                'success' => $failed_count === 0 || $delivered > 0,
                'sent' => $delivered,
                'failed' => $failed_count,
                'pruned' => (int)($fanout_payload['pruned'] ?? 0),
                'fanout' => 1,
                 
                 
                'fcm_sent' => $fcm_delivered,
                'fcm_failed' => $fcm_failed,
                'webpush_sent' => $webpush_delivered,
                'webpush_failed' => $webpush_failed,
                'target_count' => (int)($fanout_payload['targets'] ?? 0),
                'policy_blocked' => !empty($fanout['policy_blocked']),
                'error' => (string)($fanout['policy_error'] ?? ''),
            ];
        }

        if ($operator_ref_hashes) {
             
             
             
            return [
                'success' => false,
                'sent' => 0,
                'failed' => 1,
                'error' => 'targeted_push_fanout_unavailable',
            ];
        }

        if (!in_array('fcm', $channels, true)) {
             
             
            return [
                'success' => false,
                'sent' => 0,
                'failed' => 1,
                'webpush_sent' => 0,
                'webpush_failed' => 1,
                'error' => 'push_fanout_unavailable',
            ];
        }

        $tokens = $app_sessions->getAllFcmTokens();
        if (!$tokens) {
            $this->logger->write('push skipped: no registered FCM tokens and fanout unavailable');
            return ['success' => true, 'sent' => 0, 'skipped' => 'no_targets'];
        }

        foreach ($tokens as $token) {
            $delivery = $this->sendToken(
                $central_api,
                $license,
                $install_id,
                $install_secret,
                (string)$token,
                $data
            );
            if ($delivery['status'] === 'delivered') {
                $sent++;
                $app_sessions->markPushDeliveredByFcmToken((string)$token);
            } elseif ($delivery['status'] === 'pruned') {
                $pruned++;
                $app_sessions->deleteSessionsByFcmToken((string)$token);
            } else {
                $failed++;
                $app_sessions->markPushErrorByFcmToken((string)$token, (string)($delivery['error_code'] ?? 'push_delivery_failed'));
            }
        }

        return [
            'success' => $failed === 0,
            'sent' => $sent,
            'failed' => $failed,
            'pruned' => $pruned,
            'fcm_sent' => $sent,
            'fcm_failed' => $failed,
        ];
    }

    private function buildDataPayload(array $event): array {
        $source_provider = $this->normalizeProvider((string)($event['source_provider'] ?? 'widget'));
        $source_label = trim((string)($event['source_label'] ?? ''));
        $customer_name = trim((string)($event['customer_name'] ?? ''));
        $preview = trim((string)($event['preview'] ?? ''));
        $preview = $preview !== ''
            ? mb_substr($preview, 0, 100, 'UTF-8')
            : ($customer_name !== ''
            ? mb_substr($customer_name, 0, 100, 'UTF-8')
            : 'Новый диалог');

        return [
            'type' => (string)($event['type'] ?? 'new_message'),
            'chat_id' => (string)(int)($event['chat_id'] ?? 0),
            'preview' => $preview,
            'source_provider' => $source_provider,
            'source_label' => mb_substr($source_label, 0, 32, 'UTF-8'),
            'domain' => $this->getDomain(),
        ];
    }

    private function getDomain(): string {
        $domain = \EasyChat\Library\RequestContext::server()['HTTP_HOST'] ?? '';
        if ($domain !== '' && $domain !== 'localhost') {
            if (function_exists('easychat_public_host')) {
                $domain = easychat_public_host($domain);
            }
            if ($domain !== '' && $domain !== 'localhost') {
                $this->settings->set('chat_last_known_domain', $domain);
                return $domain;
            }
        }
        return (string)$this->settings->get('chat_last_known_domain', 'localhost');
    }

    private function normalizeProvider(string $provider): string {
        $provider = strtolower(trim($provider));
        $provider = preg_replace('/[^a-z0-9_-]/', '', $provider);
        return $provider !== '' ? $provider : 'widget';
    }

    private function safePublicId(string $value, string $prefix): string {
        return preg_match('/^' . preg_quote($prefix, '/') . '[a-f0-9]{24}$/D', $value) ? $value : '';
    }

    private function sendToken($central_api, $license, $install_id, $install_secret, $token, array $data): array {
        $payload = CentralGatewayClient::withBaseDomain($this->settings, [
            'domain' => $this->getDomain(),
            'fcm_token' => $token,
            'data' => $data,
        ]);
        $body = $license !== ''
            ? CentralGatewayClient::signedBody($license, $install_id, $payload)
            : CentralGatewayClient::signedUnlicensedBody($install_secret, $install_id, $payload);

        $result = CentralGatewayClient::postJson($central_api, '/api/v1/push/messages', $body, 5, $this->registry);
        $token_hash = substr(hash('sha256', $token), 0, 16);
        $mode = $license !== '' ? 'licensed' : 'trial';
        $http_status = (int)$result['status'];
        $error_code = (string)($result['error_code'] ?? '');

        if ($http_status === 200) {
            $this->logger->write("[AndroidPushNotifier] success token_hash={$token_hash}, mode={$mode}, source=" . ($data['source_provider'] ?? ''));
            return ['status' => 'delivered', 'http_status' => $http_status, 'error_code' => ''];
        }

        if ($http_status === 403 && $error_code === 'push_token_not_registered') {
            $this->logger->write("[AndroidPushNotifier] pruned token_hash={$token_hash}, mode={$mode}, error_code=push_token_not_registered");
            return ['status' => 'pruned', 'http_status' => $http_status, 'error_code' => $error_code];
        }

        $error = (string)($result['error'] ?? '');
        $this->logger->write("[AndroidPushNotifier] failed token_hash={$token_hash}, mode={$mode}, http_status={$http_status}, error_code={$error_code}, curl_error=" . mb_substr($error, 0, 120, 'UTF-8'));
        return ['status' => 'failed', 'http_status' => $http_status, 'error_code' => ($error_code !== '' ? $error_code : 'push_delivery_failed')];
    }

    private function sendFanout($central_api, $license, $install_id, $install_secret, array $data, array $channels, array $operator_ref_hashes = []): array {
        $payload = CentralGatewayClient::withBaseDomain($this->settings, [
            'domain' => $this->getDomain(),
            'audience' => 'operator',
            'channels' => $channels,
            'target' => 'domain',
            'event_id' => (string)($data['type'] ?? 'event') . ':' . (string)($data['chat_id'] ?? '0') . ':' . time(),
            'data' => $data,
        ]);
        if ($operator_ref_hashes) {
            $payload['operator_ref_hashes'] = array_values($operator_ref_hashes);
        }
        $body = $license !== ''
            ? CentralGatewayClient::signedBody($license, $install_id, $payload)
            : CentralGatewayClient::signedUnlicensedBody($install_secret, $install_id, $payload);

        $result = CentralGatewayClient::postJson($central_api, '/api/v1/push/messages', $body, 5, $this->registry);
        $json = is_array($result['json'] ?? null) ? $result['json'] : [];
        $http_status = (int)$result['status'];
        $error_code = (string)($result['error_code'] ?? '');

        if ($http_status === 200 && isset($json['fanout']) && is_array($json['fanout'])) {
            $this->logger->write('[AndroidPushNotifier] fanout success delivered=' . (int)($json['fanout']['delivered'] ?? 0) . ', failed=' . (int)($json['fanout']['failed'] ?? 0));
            return ['supported' => true, 'fanout' => $json['fanout']];
        }

        if (in_array($http_status, [402, 403], true)) {
            $this->logger->write("[AndroidPushNotifier] fanout policy response http_status={$http_status}, error_code={$error_code}");
            return [
                'supported' => true,
                'fanout' => ['delivered' => 0, 'failed' => 1, 'pruned' => 0],
                'policy_blocked' => true,
                'policy_error' => $error_code !== '' ? $error_code : 'push_policy_blocked',
            ];
        }

        $this->logger->write("[AndroidPushNotifier] fanout unavailable http_status={$http_status}, error_code={$error_code}; falling back to per-token FCM");
        return ['supported' => false];
    }

    private function channelsForEvent(string $event_type): array {
         
         
         
        if ($event_type === 'sync_chat') {
            return ['fcm'];
        }
        if ($event_type === 'chat_assignment_changed') {
            return ['fcm'];
        }

         
         
         
         
        return ['fcm', 'webpush'];
    }

    private function requestedChannels(string $event_type, array $requested): array {
        if ($event_type === 'sync_chat') {
            return ['fcm'];
        }
        if ($event_type === 'chat_assignment_changed') {
            return ['fcm'];
        }
        $channels = [];
        foreach ($requested as $channel) {
            $channel = strtolower(trim((string)$channel));
            if (in_array($channel, ['fcm', 'webpush'], true)) $channels[$channel] = true;
        }
        return $channels ? array_keys($channels) : $this->channelsForEvent($event_type);
    }

    private function activeOperatorRefHashes(): array {
        $install_id = CentralGatewayClient::ensureInstallId($this->settings);
        $hashes = [];
        foreach ((new Manager($this->registry))->getActiveManagers() as $manager) {
            $manager_id = (int)($manager['id'] ?? 0);
            if ($manager_id > 0) {
                $hashes[] = hash('sha256', $install_id . ':' . $manager_id);
            }
        }
        return $hashes;
    }
}
