<?php
namespace EasyChat\Library\Messenger;

use EasyChat\Library\Http\ProxyPolicy;
use EasyChat\Model\Messenger;
use PhpWA\Runtime\RunCheckpoint;

 
final class WhatsAppPersonalLifecycle {
    private const GENERATION_KEY = 'session_generation';
    private const SESSION_FENCE_WAIT_MICROSECONDS = 3000000;
    private const SESSION_FENCE_RETRY_MICROSECONDS = 100000;
    private $registry;
    private $db;
    private $model;

    public function __construct($registry) {
        $this->registry = $registry;
        $this->db = $registry->get('db');
        $this->model = new Messenger($registry);
    }

    public function ensureChannel(): array {
        $channel = $this->model->ensureChannel(WhatsAppPersonalProvider::PROVIDER, 'Личный WhatsApp');
        $settings = $this->settings($channel);
        $generation = max(1, (int)($settings[self::GENERATION_KEY] ?? 1));
        if ((int)($settings[self::GENERATION_KEY] ?? 0) !== $generation) {
            $settings[self::GENERATION_KEY] = $generation;
            $this->model->updateChannel((int)$channel['channel_id'], ['settings_json' => $this->json($settings)]);
            $channel = $this->model->getChannelById((int)$channel['channel_id']);
        }
         
         
         
         
        return $channel;
    }

     
    public function beginAuthentication(array $channel): array {
        return $this->locked((int)$channel['channel_id'], function (array $fresh): array {
            $settings = $this->settings($fresh);
            $generation = max(1, (int)($settings[self::GENERATION_KEY] ?? 1)) + 1;
            $attempt_id = bin2hex(random_bytes(12));
            $now = time();
            $settings[self::GENERATION_KEY] = $generation;
            $settings['auth'] = [
                'status' => 'qr_starting', 'attempt_id' => $attempt_id,
                'generation' => $generation, 'created_at' => $now,
                'expires_at' => $now + 180, 'worker_heartbeat_at' => 0,
            ];
            $settings['last_error_code'] = '';

             
             
            WhatsAppPersonalRuntime::publishGeneration((int)$fresh['channel_id'], $generation);
            $this->model->updateChannel((int)$fresh['channel_id'], [
                'status' => 'disabled', 'settings_json' => $this->json($settings), 'last_error' => '',
            ]);
            $updated = $this->model->getChannelById((int)$fresh['channel_id']);

             
             
            $client = $this->client($updated, $generation);
            try {
                $this->discardSessionAfterGenerationFence(static function () use ($client): void {
                    $client->discardLocalSessionAndPendingWork();
                });
            } catch (\Throwable $error) {
                 
                 
                $settings['auth'] = array_merge($settings['auth'], [
                    'status' => 'error',
                    'error_code' => 'session_prepare_busy',
                    'worker_heartbeat_at' => time(),
                ]);
                $this->model->updateChannel((int)$fresh['channel_id'], [
                    'status' => 'disabled',
                    'settings_json' => $this->json($settings),
                    'last_error' => 'session_prepare_busy',
                ]);
                throw $error;
            }
            return ['channel' => $updated, 'generation' => $generation, 'attempt_id' => $attempt_id];
        });
    }

    public function updateAuth(int $channel_id, int $generation, string $attempt_id, array $patch): bool {
        return (bool)$this->locked($channel_id, function (array $fresh) use ($generation, $attempt_id, $patch): bool {
            $settings = $this->settings($fresh);
            $auth = is_array($settings['auth'] ?? null) ? $settings['auth'] : [];
            if ((int)($settings[self::GENERATION_KEY] ?? 0) !== $generation
                || !hash_equals((string)($auth['attempt_id'] ?? ''), $attempt_id)) {
                return false;
            }
            $settings['auth'] = array_merge($auth, $patch);
            $this->model->updateChannel((int)$fresh['channel_id'], ['settings_json' => $this->json($settings)]);
            return true;
        });
    }

    public function completeAuthentication(int $channel_id, int $generation, string $attempt_id, array $identity): bool {
        return (bool)$this->locked($channel_id, function (array $fresh) use ($channel_id, $generation, $attempt_id, $identity): bool {
            $settings = $this->settings($fresh);
            $auth = is_array($settings['auth'] ?? null) ? $settings['auth'] : [];
            if ((int)($settings[self::GENERATION_KEY] ?? 0) !== $generation
                || !hash_equals((string)($auth['attempt_id'] ?? ''), $attempt_id)) {
                return false;
            }
            $display_name = $this->safeLabel((string)($identity['display_name'] ?? 'Личный WhatsApp'));
            $pn = $this->safeId((string)($identity['pn_jid'] ?? ''));
            $lid = $this->safeId((string)($identity['lid_jid'] ?? ''));
            $settings['auth'] = ['status' => 'connected', 'generation' => $generation, 'connected_at' => time()];
            $settings['account_profile'] = ['display_name' => $display_name, 'pn_jid' => $pn, 'lid_jid' => $lid];
            $settings['last_error_code'] = '';
            $this->model->updateChannel($channel_id, [
                'status' => 'active',
                'external_account_id' => $lid !== '' ? $lid : $pn,
                'bot_user_id' => $lid !== '' ? $lid : $pn,
                'bot_name' => $display_name,
                'settings_json' => $this->json($settings),
                'last_error' => '',
            ]);
            return true;
        });
    }

     
    public function updateWorkerState(int $channel_id, int $generation, array $state, array $channel_patch = []): bool {
        return (bool)$this->locked($channel_id, function (array $fresh) use ($channel_id, $generation, $state, $channel_patch): bool {
            $settings = $this->settings($fresh);
            if ((int)($settings[self::GENERATION_KEY] ?? 0) !== $generation) {
                return false;
            }
            $settings['worker'] = array_merge(
                is_array($settings['worker'] ?? null) ? $settings['worker'] : [],
                $state
            );
            $channel_patch['settings_json'] = $this->json($settings);
            $this->model->updateChannel($channel_id, $channel_patch);
            return true;
        });
    }

    




    public function recordCronTick(int $channel_id, int $generation, ?int $timestamp = null): bool {
        $timestamp = $timestamp ?? time();
        return (bool)$this->locked($channel_id, function (array $fresh) use ($channel_id, $generation, $timestamp): bool {
            $settings = $this->settings($fresh);
            if ((int)($settings[self::GENERATION_KEY] ?? 0) !== $generation) {
                return false;
            }
            $worker = is_array($settings['worker'] ?? null) ? $settings['worker'] : [];
            $runs = is_array($worker['cron_recent_runs'] ?? null) ? $worker['cron_recent_runs'] : [];
            $runs[] = $timestamp;
            $worker['cron_recent_runs'] = array_slice(array_values(array_filter(
                array_map('intval', $runs),
                static function (int $value) use ($timestamp): bool {
                    return $value > 0 && $value <= $timestamp + 60 && $value >= $timestamp - 21600;
                }
            )), -30);

            $worker['cron_first_seen_at'] = max(1, min(
                $timestamp,
                max(1, (int)($worker['cron_first_seen_at'] ?? $timestamp))
            ));
            $worker['cron_quarter_runs'] = $this->incrementCronBucket(
                is_array($worker['cron_quarter_runs'] ?? null) ? $worker['cron_quarter_runs'] : [],
                900,
                32,
                $timestamp
            );
            $worker['cron_hourly_runs'] = $this->incrementCronBucket(
                is_array($worker['cron_hourly_runs'] ?? null) ? $worker['cron_hourly_runs'] : [],
                3600,
                168,
                $timestamp
            );
            $worker['cron_last_seen_at'] = $timestamp;
            $settings['worker'] = $worker;
            $this->model->updateChannel($channel_id, ['settings_json' => $this->json($settings)]);
            return true;
        });
    }

    public function clear(array $channel): void {
        $this->locked((int)$channel['channel_id'], function (array $fresh): bool {
            $settings = $this->settings($fresh);
            $generation = max(1, (int)($settings[self::GENERATION_KEY] ?? 1)) + 1;
            $settings[self::GENERATION_KEY] = $generation;
            unset($settings['auth'], $settings['account_profile']);
            $settings['last_error_code'] = '';
            WhatsAppPersonalRuntime::publishGeneration((int)$fresh['channel_id'], $generation);
            $this->model->updateChannel((int)$fresh['channel_id'], [
                'status' => 'disabled', 'external_account_id' => '', 'bot_user_id' => '',
                'bot_name' => '', 'settings_json' => $this->json($settings), 'last_error' => '',
            ]);
            $client = $this->client($this->model->getChannelById((int)$fresh['channel_id']), $generation);
            $this->discardSessionAfterGenerationFence(static function () use ($client): void {
                $client->discardLocalSessionAndPendingWork();
            });
            return true;
        });
    }

    




    private function discardSessionAfterGenerationFence(callable $discard): void {
        $deadline = microtime(true) + (self::SESSION_FENCE_WAIT_MICROSECONDS / 1000000);
        while (true) {
            try {
                call_user_func($discard);
                return;
            } catch (\PhpWA\Exception\AccountBusyException $error) {
                if (microtime(true) >= $deadline) {
                    throw $error;
                }
                usleep(self::SESSION_FENCE_RETRY_MICROSECONDS);
            }
        }
    }

    public function setReceivingEnabled(int $channel_id, bool $enabled): array {
        return (array)$this->locked($channel_id, function (array $fresh) use ($enabled): array {
            $settings = $this->settings($fresh);
            $auth = is_array($settings['auth'] ?? null) ? $settings['auth'] : [];
            if ($enabled && (string)($auth['status'] ?? '') !== 'connected') {
                return ['success' => false, 'error' => 'authorization_required'];
            }
            $settings['receiving_enabled'] = $enabled ? 1 : 0;
            $this->model->updateChannel($channel_id, [
                'status' => $enabled ? 'active' : 'disabled',
                'settings_json' => $this->json($settings),
            ]);
            return ['success' => true, 'enabled' => $enabled];
        });
    }

    public function generation(int $channel_id): int {
        $stored = WhatsAppPersonalRuntime::generation($channel_id);
        if ($stored > 0) {
            return $stored;
        }
        $channel = $this->model->getChannelById($channel_id);
        return max(1, (int)($this->settings($channel)[self::GENERATION_KEY] ?? 1));
    }

    



    public function storedGeneration(array $channel): int {
        return max(1, (int)($this->settings($channel)[self::GENERATION_KEY] ?? 1));
    }

    public function client(array $channel, int $generation): \PhpWA\Client {
        $channel_id = (int)$channel['channel_id'];
        $last_heartbeat = 0;
        return WhatsAppPersonalRuntime::client(
            $channel,
            $generation,
            static function () use ($channel_id): int {
                return WhatsAppPersonalRuntime::generation($channel_id);
            },
            function (RunCheckpoint $checkpoint) use ($channel_id, $generation, &$last_heartbeat): void {
                $now = time();
                if ($now - $last_heartbeat < 5) {
                    return;
                }
                $last_heartbeat = $now;
                $this->heartbeat($channel_id, $generation, $checkpoint, $now);
            },
            $this->proxyConnector()
        );
    }

    public function proxyConnector(): ?\PhpWA\Transport\StreamConnectorInterface {
        $proxy = (new ProxyPolicy($this->registry))->proxyForProfile('messenger_whatsapp');
        if ($proxy === null) {
            return null;
        }
        WhatsAppPersonalRuntime::requireAutoload();
        return new WhatsAppPersonalProxyConnector($proxy);
    }

    private function heartbeat(int $channel_id, int $generation, RunCheckpoint $checkpoint, int $now): void {
        $this->updateWorkerState($channel_id, $generation, [
            'heartbeat_at' => $now,
            'state' => substr($checkpoint->state(), 0, 24),
            'phase' => substr($checkpoint->phase(), 0, 32),
            'nodes' => max(0, $checkpoint->nodesProcessed()),
        ]);
    }

    



    private function incrementCronBucket(array $buckets, int $seconds, int $limit, int $timestamp): array {
        $start = intdiv(max(0, $timestamp), $seconds) * $seconds;
        $normalized = [];
        foreach ($buckets as $bucket) {
            if (!is_array($bucket)) {
                continue;
            }
            $bucket_start = max(0, (int)($bucket[0] ?? 0));
            if ($bucket_start <= 0) {
                continue;
            }
            $normalized[$bucket_start] = [
                $bucket_start,
                min(120, max(0, (int)($bucket[1] ?? 0))),
            ];
        }
        $normalized[$start] = [
            $start,
            min(120, max(0, (int)($normalized[$start][1] ?? 0)) + 1),
        ];
        ksort($normalized, SORT_NUMERIC);
        return array_slice(array_values($normalized), -max(1, $limit));
    }

    private function locked(int $channel_id, callable $callback) {
        if ($channel_id <= 0) { throw new \InvalidArgumentException('whatsapp_channel_invalid'); }
        $name = 'easychat_wa_' . substr(hash('sha256', (string)$channel_id), 0, 32);
        $lock = $this->db->query("SELECT GET_LOCK('" . $this->db->escape($name) . "', 5) AS `locked`");
        if ((int)($lock->row['locked'] ?? 0) !== 1) { throw new \RuntimeException('whatsapp_lifecycle_busy'); }
        try {
            $fresh = $this->model->getChannelById($channel_id);
            if (!$fresh || (string)($fresh['provider'] ?? '') !== WhatsAppPersonalProvider::PROVIDER) {
                throw new \RuntimeException('whatsapp_channel_not_found');
            }
            return call_user_func($callback, $fresh);
        } finally {
            $this->db->query("SELECT RELEASE_LOCK('" . $this->db->escape($name) . "')");
        }
    }

    private function settings(array $channel): array {
        $settings = json_decode((string)($channel['settings_json'] ?? '{}'), true);
        return is_array($settings) ? $settings : [];
    }

    private function json(array $value): string {
        return (string)json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
    }

    private function safeLabel(string $value): string {
        $value = trim((string)preg_replace('/\s+/u', ' ', strip_tags($value)));
        return mb_substr($value !== '' ? $value : 'Личный WhatsApp', 0, 120, 'UTF-8');
    }

    private function safeId(string $value): string {
        $value = trim($value);
        return preg_match('/^[0-9]+(?::[0-9]+)?@(s\.whatsapp\.net|lid)$/', $value) ? substr($value, 0, 128) : '';
    }
}
