<?php
namespace EasyChat\Controller;

use EasyChat\Library\PresenceService;
use EasyChat\Library\SafeRuntime;
use EasyChat\Library\AndroidPushNotifier;
use EasyChat\Library\Notification\TrialLockGuard;
use EasyChat\Library\Operator\OperatorAvatarService;
use EasyChat\Library\Security\PasswordCredentialService;
use EasyChat\Model\Manager;
use EasyChat\Model\SmsDevice;
use EasyChat\Model\SmsJobEvent;
use EasyChat\Model\AppDevice;
use EasyChat\Model\AppPairing;
use EasyChat\Model\AppSession;
use EasyChat\View\HtmxFragmentRenderer;
use EasyChat\View\OperatorFragments;

class Operator extends Base {

    protected $layouts = 'layout';

    private $operator_fragments;
    private $htmx_fragment_renderer;

    public function __construct($registry) {
        parent::__construct($registry);
         
        if (!isset(\EasyChat\Library\RequestContext::session()['manager_id'])) {
            header('Location: index.php?route=auth');
            exit;
        }
        if (\EasyChat\Library\RequestContext::session()['manager_role'] !== 'admin') {
            header('Location: index.php?route=index');
            exit;
        }
    }

    public function index() {
        $this->vars['title'] = 'Операторы | EasyChat';

        $this->vars['operators'] = $this->preparedOperators(new Manager($this->registry));

        $this->template->view('operator');
    }

    public function createDevicePairing() {
        header('Content-Type: application/json; charset=utf-8');
        header('Cache-Control: no-store');
        $this->requireCsrfTokenJson();

        $manager_id = (int)(\EasyChat\Library\RequestContext::post()['manager_id'] ?? 0);
        $manager = (new Manager($this->registry))->getManagerById($manager_id);
        if (!$manager || (int)$manager['status'] !== 1) {
            http_response_code(404);
            echo json_encode(['success' => false, 'error' => 'Активный оператор не найден.'], JSON_UNESCAPED_UNICODE);
            exit;
        }

        $pairing = (new AppPairing($this->registry))->create(
            $manager_id,
            AppPairing::DEFAULT_TTL_SECONDS,
            $this->pairingClientIp()
        );
        $payload = [
            'v' => 1,
            'site_url' => rtrim((string)HTTP_SERVER, '/') . '/',
            'pairing_id' => (string)$pairing['public_id'],
            'pairing_secret' => (string)$pairing['secret'],
            'expires_at' => (string)$pairing['expires_at'],
            'source' => 'easychat_admin_operator',
        ];
        $packed = rtrim(strtr(base64_encode(json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)), '+/', '-_'), '=');

        echo json_encode([
            'success' => true,
             
             
            'connect_url' => 'https://easychat24.ru/app/connect/#p=' . $packed,
            'expires_at' => (string)$pairing['expires_at'],
            'ttl_seconds' => AppPairing::DEFAULT_TTL_SECONDS,
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }

     
    public function save() {
        $this->requireCsrfTokenJson();

        $id       = isset(\EasyChat\Library\RequestContext::post()['id']) ? (int)\EasyChat\Library\RequestContext::post()['id'] : 0;
        $username = isset(\EasyChat\Library\RequestContext::post()['username']) ? trim(\EasyChat\Library\RequestContext::post()['username']) : '';
        $name     = isset(\EasyChat\Library\RequestContext::post()['name']) ? trim(\EasyChat\Library\RequestContext::post()['name']) : '';
        $password = isset(\EasyChat\Library\RequestContext::post()['password']) ? (string)\EasyChat\Library\RequestContext::post()['password'] : '';
        $role     = isset(\EasyChat\Library\RequestContext::post()['role']) ? trim(\EasyChat\Library\RequestContext::post()['role']) : 'manager';
        $status   = isset(\EasyChat\Library\RequestContext::post()['status']) ? (int)\EasyChat\Library\RequestContext::post()['status'] : 1;
        $ai_suggestions_mode = isset(\EasyChat\Library\RequestContext::post()['ai_suggestions_mode']) ? trim((string)\EasyChat\Library\RequestContext::post()['ai_suggestions_mode']) : 'inherit';
        $show_in_widget_header = isset(\EasyChat\Library\RequestContext::post()['show_in_widget_header'])
            ? (int)\EasyChat\Library\RequestContext::post()['show_in_widget_header']
            : 1;

        $model_manager = new Manager($this->registry);
        $status = $status === 1 ? 1 : 0;
        $show_in_widget_header = $show_in_widget_header === 1 ? 1 : 0;

        if ($id === 1) {
            $admin = $model_manager->getManagerById(1);
            $username = $admin['username'];
            $role = 'admin';
            $status = 1;
            if ($name === '') {
                $name = $admin['name'];
            }
        } else {
            if (!$username || !$name) {
                $this->renderOperatorModalAlert('Логин и Имя являются обязательными полями', 422);
            }

             
            if ($model_manager->isUsernameTaken($username, $id)) {
                $this->renderOperatorModalAlert('Пользователь с таким логином уже существует', 409);
            }

            if ($id === 0 && $password === '') {
                $this->renderOperatorModalAlert('Пароль обязателен для нового оператора', 422);
            }
        }

        $permission_delete_chats = isset(\EasyChat\Library\RequestContext::post()['permission_delete_chats']) ? (int)\EasyChat\Library\RequestContext::post()['permission_delete_chats'] : 0;
        $permission_manage_chat_assignments = isset(\EasyChat\Library\RequestContext::post()['permission_manage_chat_assignments'])
            ? (int)\EasyChat\Library\RequestContext::post()['permission_manage_chat_assignments']
            : 0;
        $ai_suggestions_mode = $model_manager->normalizeAiSuggestionsMode($ai_suggestions_mode);

        if ($password !== '') {
            $password_validation = (new PasswordCredentialService())->validate($password, $username, $name);
            if (empty($password_validation['valid'])) {
                $this->renderOperatorModalAlert((string)$password_validation['message'], 422);
            }
        }

        $db = $this->registry->get('db');
        $transaction_open = false;
        try {
            $db->query('START TRANSACTION');
            $transaction_open = true;
            $saved_id = $model_manager->saveManager($id, [
                'username' => $username,
                'name'     => $name,
                'password' => $password,
                'role'     => $role,
                'status'   => $status,
                'permission_delete_chats' => $permission_delete_chats,
                'permission_manage_chat_assignments' => $permission_manage_chat_assignments,
                'ai_suggestions_mode' => $ai_suggestions_mode,
                'show_in_widget_header' => $show_in_widget_header
            ]);

            if ($id > 0 && $password !== '') {
                 
                 
                (new AppSession($this->registry))->revokeManagerSessions(
                    $id,
                    'password_changed_by_admin'
                );
            }

            if ($id === 1 && $password !== '') {
                $model_manager->markAdminPasswordCustomized();
            }
            $db->query('COMMIT');
            $transaction_open = false;
        } catch (\Throwable $e) {
            if ($transaction_open) {
                try {
                    $db->query('ROLLBACK');
                } catch (\Throwable $ignored) {
                }
            }
            throw $e;
        }

        if ($id === 1 && $password !== '') {
            unset(\EasyChat\Library\RequestContext::session()['suggest_password_change']);

            $admin = $model_manager->getManagerById(1);
            if ($admin) {
                \EasyChat\Library\RequestContext::session()['manager_pass_hash'] = md5($admin['password']);
            }
        }

        if ($id === (int)\EasyChat\Library\RequestContext::session()['manager_id']) {
            \EasyChat\Library\RequestContext::session()['manager_name'] = $name;
        }

        $this->renderOperatorRowsPartial([
            'easychat:operator-saved' => [
                'id' => (int)$saved_id,
                'mode' => $id > 0 ? 'updated' : 'created',
            ],
        ]);
    }

     
    public function delete() {
        $this->requireCsrfTokenJson();

        $id = isset(\EasyChat\Library\RequestContext::post()['id']) ? (int)\EasyChat\Library\RequestContext::post()['id'] : 0;

        if ($id <= 0) {
            $this->renderOperatorPageAlert('Неверный ID', 422);
        }

        if ($id === 1 || $id === (int)\EasyChat\Library\RequestContext::session()['manager_id']) {
            $this->renderOperatorPageAlert('Нельзя удалить главного администратора или самого себя', 403);
        }

        $model_manager = new Manager($this->registry);

         
        $old_image = $model_manager->getAvatar($id);
        if ($old_image) {
            $file = DIR_AVATARS . $old_image;
            if (file_exists($file)) {
                SafeRuntime::unlink($file);
            }
        }

        $model_manager->deleteManager($id);

        $this->htmxFragments()->sendHtml('', 200, [
            'HX-Trigger' => json_encode([
                'easychat:operator-deleted' => [
                    'id' => (int)$id,
                ],
            ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
        ]);
    }

    public function requestSmsPermission() {
        header('Content-Type: application/json; charset=utf-8');
        $this->requireCsrfTokenJson();

        $device_id = isset(\EasyChat\Library\RequestContext::post()['device_id']) ? (int)\EasyChat\Library\RequestContext::post()['device_id'] : 0;
        if ($device_id <= 0) {
            echo json_encode(['success' => false, 'error' => 'Телефон не найден.'], JSON_UNESCAPED_UNICODE);
            exit;
        }

        $model = new SmsDevice($this->registry);
        $device = $model->getById($device_id);
        if (!$device) {
            echo json_encode(['success' => false, 'error' => 'Телефон не найден.'], JSON_UNESCAPED_UNICODE);
            exit;
        }
        if ((new TrialLockGuard($this->registry))->isSmsBlocked(false)) {
            echo json_encode([
                'success' => false,
                'code' => 'trial_limit_exceeded',
                'error' => 'Бесплатный лимит push-уведомлений исчерпан. SMS через телефон оператора доступны после подключения лицензии EasyChat.',
            ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            exit;
        }
        $model->requestPermission($device_id);
        $push = (new AndroidPushNotifier($this->registry))->notifySmsPermissionRequest((int)$device['app_session_id']);

        echo json_encode([
            'success' => true,
            'message' => 'Запрос отправлен в приложение оператора.',
            'push' => $push,
            'html' => $this->operatorRowsHtml(),
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }

    public function devices() {
        header('Content-Type: application/json; charset=utf-8');
        $manager_id = (int)(\EasyChat\Library\RequestContext::get()['manager_id'] ?? 0);
        $manager = (new Manager($this->registry))->getManagerById($manager_id);
        if (!$manager) {
            echo json_encode(['success' => false, 'error' => 'Оператор не найден.'], JSON_UNESCAPED_UNICODE);
            exit;
        }
        $devices = (new AppDevice($this->registry))->listForManager($manager_id, true);
        echo json_encode([
            'success' => true,
            'html' => $this->operatorFragments()->deviceCenter($manager, $devices),
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }

    public function renameDevice() {
        header('Content-Type: application/json; charset=utf-8');
        $this->requireCsrfTokenJson();
        $device_id = (int)(\EasyChat\Library\RequestContext::post()['device_id'] ?? 0);
        $name = trim((string)(\EasyChat\Library\RequestContext::post()['name'] ?? ''));
        if ($device_id <= 0 || $name === '' || mb_strlen($name, 'UTF-8') > 120) {
            echo json_encode(['success' => false, 'error' => 'Введите название длиной до 120 символов.'], JSON_UNESCAPED_UNICODE);
            exit;
        }
        $model = new AppDevice($this->registry);
        $device = $model->getById($device_id);
        if (!$device || !$model->rename($device_id, (int)$device['manager_id'], $name)) {
            echo json_encode(['success' => false, 'error' => 'Устройство не найдено.'], JSON_UNESCAPED_UNICODE);
            exit;
        }
        $model->recordEvent($device_id, (int)$device['manager_id'], 'renamed', 'admin_action');
        $this->deviceMutationResponse((int)$device['manager_id'], 'Название устройства сохранено.');
    }

    public function revokeDevice() {
        header('Content-Type: application/json; charset=utf-8');
        $this->requireCsrfTokenJson();
        $device_id = (int)(\EasyChat\Library\RequestContext::post()['device_id'] ?? 0);
        $model = new AppDevice($this->registry);
        $device = $model->getById($device_id);
        if (!$device || (string)$device['status'] !== 'active') {
            echo json_encode(['success' => false, 'error' => 'Активное устройство не найдено.'], JSON_UNESCAPED_UNICODE);
            exit;
        }
        $db = $this->registry->get('db');
        $prefix = easychat_db_prefix();
        $transaction_open = false;
        try {
            $db->query('START TRANSACTION');
            $transaction_open = true;
            (new AppSession($this->registry))->revokeDeviceSessions($device_id, 'admin_revoked');
            $reserved = $db->query(
                "SELECT `job_id` FROM `{$prefix}sms_jobs`
                 WHERE `app_device_id` = {$device_id} AND `status` = 'reserved'
                 LIMIT 1 FOR UPDATE"
            );
            if ($reserved->num_rows) {
                $db->query('ROLLBACK');
                $transaction_open = false;
                echo json_encode([
                    'success' => false,
                    'error' => 'Телефон завершает отправку SMS. Отзыв станет доступен после завершения текущей задачи.',
                ], JSON_UNESCAPED_UNICODE);
                exit;
            }
            $model->revoke($device_id, 'admin_revoked');
            $db->query(
                "UPDATE `{$prefix}sms_devices` SET
                    `admin_requested` = 0, `operator_consent` = 'none',
                    `status` = 'disabled_by_admin',
                    `inbound_admin_requested` = 0,
                    `inbound_operator_consent` = 'none',
                    `inbound_status` = 'disabled_by_admin',
                    `date_modified` = NOW()
                 WHERE `app_device_id` = {$device_id}"
            );
            $db->query(
                "UPDATE `{$prefix}sms_jobs` SET
                    `status` = 'failed', `last_error` = 'device_revoked',
                    `last_stage` = 'device_revoked',
                    `last_stage_code` = 'device_revoked',
                    `last_stage_at` = NOW(), `date_modified` = NOW()
                 WHERE `app_device_id` = {$device_id} AND `status` = 'pending'"
            );
            $model->recordEvent($device_id, (int)$device['manager_id'], 'revoked', 'admin_action');
            $db->query('COMMIT');
            $transaction_open = false;
        } catch (\Throwable $e) {
            if ($transaction_open) {
                try { $db->query('ROLLBACK'); } catch (\Throwable $ignored) {}
            }
            throw $e;
        }
        $this->deviceMutationResponse((int)$device['manager_id'], 'Доступ устройства отозван.');
    }

    private function deviceMutationResponse($manager_id, $message) {
        $manager = (new Manager($this->registry))->getManagerById((int)$manager_id);
        $devices = (new AppDevice($this->registry))->listForManager((int)$manager_id, true);
        echo json_encode([
            'success' => true,
            'message' => (string)$message,
            'html' => $this->operatorFragments()->deviceCenter($manager ?: [], $devices),
            'rows_html' => $this->operatorRowsHtml(),
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }

    public function revokeSmsPermission() {
        header('Content-Type: application/json; charset=utf-8');
        $this->requireCsrfTokenJson();

        $device_id = isset(\EasyChat\Library\RequestContext::post()['device_id']) ? (int)\EasyChat\Library\RequestContext::post()['device_id'] : 0;
        if ($device_id <= 0) {
            echo json_encode(['success' => false, 'error' => 'Телефон не найден.'], JSON_UNESCAPED_UNICODE);
            exit;
        }

        $model = new SmsDevice($this->registry);
        $device = $model->getById($device_id);
        if (!$device) {
            echo json_encode(['success' => false, 'error' => 'Телефон не найден.'], JSON_UNESCAPED_UNICODE);
            exit;
        }

        $model->revokePermission($device_id);

        echo json_encode([
            'success' => true,
            'message' => 'Отправка SMS с телефона оператора отключена.',
            'html' => $this->operatorRowsHtml(),
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }

    public function requestSmsInboundPermission() {
        header('Content-Type: application/json; charset=utf-8');
        $this->requireCsrfTokenJson();

        $device_id = isset(\EasyChat\Library\RequestContext::post()['device_id']) ? (int)\EasyChat\Library\RequestContext::post()['device_id'] : 0;
        if ($device_id <= 0) {
            echo json_encode(['success' => false, 'error' => 'Телефон не найден.'], JSON_UNESCAPED_UNICODE);
            exit;
        }

        $model = new SmsDevice($this->registry);
        $device = $model->getById($device_id);
        if (!$device) {
            echo json_encode(['success' => false, 'error' => 'Телефон не найден.'], JSON_UNESCAPED_UNICODE);
            exit;
        }
        if ((new TrialLockGuard($this->registry))->isSmsBlocked(false)) {
            echo json_encode([
                'success' => false,
                'code' => 'trial_limit_exceeded',
                'error' => 'Бесплатный лимит push-уведомлений исчерпан. Прием SMS-ответов доступен после подключения лицензии EasyChat.',
            ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            exit;
        }
        $model->requestInboundPermission($device_id);
        $push = (new AndroidPushNotifier($this->registry))->notifySmsInboundPermissionRequest((int)$device['app_session_id']);

        echo json_encode([
            'success' => true,
            'message' => 'Запрос на прием SMS-ответов отправлен в приложение оператора.',
            'push' => $push,
            'html' => $this->operatorRowsHtml(),
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }

    public function revokeSmsInboundPermission() {
        header('Content-Type: application/json; charset=utf-8');
        $this->requireCsrfTokenJson();

        $device_id = isset(\EasyChat\Library\RequestContext::post()['device_id']) ? (int)\EasyChat\Library\RequestContext::post()['device_id'] : 0;
        if ($device_id <= 0) {
            echo json_encode(['success' => false, 'error' => 'Телефон не найден.'], JSON_UNESCAPED_UNICODE);
            exit;
        }

        $model = new SmsDevice($this->registry);
        $device = $model->getById($device_id);
        if (!$device) {
            echo json_encode(['success' => false, 'error' => 'Телефон не найден.'], JSON_UNESCAPED_UNICODE);
            exit;
        }

        $model->revokeInboundPermission($device_id);
        try {
            $push = (new AndroidPushNotifier($this->registry))->notifySmsInboundPermissionRevoked(
                (int)$device['app_session_id']
            );
        } catch (\Throwable $e) {
             
             
            $push = ['success' => false, 'sent' => 0, 'failed' => 1, 'error' => 'push_delivery_failed'];
        }

        echo json_encode([
            'success' => true,
            'message' => 'Прием SMS-ответов с телефона оператора отключен.',
            'push' => $push,
            'html' => $this->operatorRowsHtml(),
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }

    public function smsDiagnostics() {
        header('Content-Type: application/json; charset=utf-8');
        $device_id = (int)(\EasyChat\Library\RequestContext::get()['device_id'] ?? 0);
        if ($device_id <= 0) {
            echo json_encode(['success' => false, 'error' => 'Телефон не найден.'], JSON_UNESCAPED_UNICODE);
            exit;
        }

        $device = (new SmsDevice($this->registry))->getById($device_id);
        if (!$device) {
            echo json_encode(['success' => false, 'error' => 'Телефон не найден.'], JSON_UNESCAPED_UNICODE);
            exit;
        }
        $jobs = (new SmsJobEvent($this->registry))->recentForDevice($device_id, 8);
        echo json_encode([
            'success' => true,
            'html' => $this->operatorFragments()->smsDiagnostics($device, $jobs),
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }

     
    public function uploadAvatar() {
        header('Content-Type: application/json; charset=utf-8');
        $this->requireCsrfTokenJson();

        $manager_id = isset(\EasyChat\Library\RequestContext::post()['manager_id']) ? (int)\EasyChat\Library\RequestContext::post()['manager_id'] : 0;
        if (!$manager_id) {
            echo json_encode(['success' => false, 'error' => 'Оператор не найден'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            exit;
        }

        $model_manager = new Manager($this->registry);
        $manager = $model_manager->getManagerById($manager_id);
        if (!$manager) {
            echo json_encode(['success' => false, 'error' => 'Оператор не найден'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            exit;
        }

        $result = (new OperatorAvatarService($this->registry))->upload(
            $manager_id,
            (array)(\EasyChat\Library\RequestContext::files()['file'] ?? [])
        );
        if (empty($result['success'])) {
            http_response_code(422);
            echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            exit;
        }
        $filename = (string)$result['filename'];

         
        if ($manager_id === (int)\EasyChat\Library\RequestContext::session()['manager_id']) {
            \EasyChat\Library\RequestContext::session()['manager_img'] = $filename;
        }

        $operators = $this->preparedOperators($model_manager);
        $updated_operator = null;
        foreach ($operators as $operator) {
            if ((int)($operator['id'] ?? 0) === $manager_id) {
                $updated_operator = $operator;
                break;
            }
        }
        if ($updated_operator === null) {
            $updated_operator = $model_manager->getManagerById($manager_id);
            $updated_operator['sms_devices'] = [];
            $updated_operator['app_sessions'] = 0;
            $updated_operator['is_online'] = 0;
        }

        echo json_encode([
            'success' => true,
            'image' => (string)$result['url'],
            'image_file' => $filename,
            'avatar_html' => $this->operatorFragments()->avatarCell($updated_operator),
            'row_html' => $this->operatorFragments()->row($updated_operator),
            'message' => 'Фото профиля обновлено.',
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }

    private function preparedOperators(Manager $model_manager) {
        $operators = $model_manager->getManagers();
        $presence = new PresenceService($this->registry);

        foreach ($operators as &$op) {
            $op['is_online'] = ((int)$op['status'] === 1 && $presence->isLastActiveOnline($op['last_active'] ?? '')) ? 1 : 0;
            $op['sms_devices'] = (new SmsDevice($this->registry))->listForManager((int)$op['id']);
            $op['app_devices'] = (new AppDevice($this->registry))->listForManager((int)$op['id'], true);
        }
        unset($op);

        return $operators;
    }

    private function renderOperatorRowsPartial(array $trigger = []) {
        $this->htmxFragments()->sendFragment(
            $this->operatorRowsHtml(),
            $trigger
        );
    }

    private function operatorRowsHtml() {
        $model_manager = new Manager($this->registry);

        return $this->operatorFragments()->rows($this->preparedOperators($model_manager));
    }

    private function pairingClientIp() {
        $ip = trim((string)(\EasyChat\Library\RequestContext::server()['REMOTE_ADDR'] ?? ''));
        return strlen($ip) <= 45 ? $ip : '';
    }

    private function renderOperatorModalAlert($message, $status = 400) {
        $this->htmxFragments()->sendAlert(
            '#modal-alert',
            $this->operatorFragments()->modalAlert((string)$message, false),
            (int)$status
        );
    }

    private function renderOperatorPageAlert($message, $status = 400) {
        $this->htmxFragments()->sendAlert(
            '#operator-error-alert',
            $this->operatorFragments()->alert('error', (string)$message, false),
            (int)$status
        );
    }

    private function operatorFragments() {
        if (!$this->operator_fragments instanceof OperatorFragments) {
            $this->operator_fragments = new OperatorFragments();
        }
        return $this->operator_fragments;
    }

    private function htmxFragments() {
        if (!$this->htmx_fragment_renderer instanceof HtmxFragmentRenderer) {
            $this->htmx_fragment_renderer = new HtmxFragmentRenderer();
        }
        return $this->htmx_fragment_renderer;
    }
}
