<?php
namespace EasyChat\Library\Operator;

use EasyChat\Library\Messenger\SecretBox;
use EasyChat\Library\Messenger\ReplyCapability;
use EasyChat\Model\Message;
use EasyChat\Model\OperatorDraft;
use EasyChat\Model\Session;
use EasyChat\Model\Setting;







class OperatorDraftService {
    public const MAX_TEXT_LENGTH = 10000;
    public const SCOPE_REPLY = 'reply';

    private $registry;
    private $model;
    private $secret;

    public function __construct($registry) {
        $this->registry = $registry;
        $this->model = new OperatorDraft($registry);
        $this->secret = trim((string)(new Setting($registry))->get('chat_install_secret', ''));
    }

    public function get(int $manager_id, int $chat_id): array {
        if (!$this->chatExists($chat_id)) return $this->fail('chat_not_found', 404);
        if (empty((new ReplyCapability($this->registry))->forChatId($chat_id)['can_reply'])) {
             
             
            return ['success' => true, 'operator_draft' => $this->emptyPayload($chat_id)];
        }
        try {
            $row = $this->model->find($manager_id, $chat_id);
        } catch (\Throwable $e) {
             
             
             
            $row = [];
        }
        return ['success' => true, 'operator_draft' => $this->payload($row, $manager_id, $chat_id)];
    }

    public function summariesForChats(int $manager_id, array $chat_ids): array {
        $result = [];
        try {
            $rows = $this->model->rowsForChats($manager_id, $chat_ids);
        } catch (\Throwable $e) {
            return [];
        }
        foreach ($rows as $chat_id => $row) {
            $payload = $this->payload($row, $manager_id, (int)$chat_id);
            if (!empty($payload['exists'])) $result[(int)$chat_id] = $this->summary($payload);
        }
        return $result;
    }

    public function save(array $input): array {
        $manager_id = (int)($input['manager_id'] ?? 0);
        $chat_id = (int)($input['chat_id'] ?? 0);
        $base_revision = max(0, (int)($input['base_revision'] ?? 0));
        $mutation_id = trim((string)($input['mutation_id'] ?? ''));
        $text = (string)($input['text'] ?? '');
        $surface = strtolower(trim((string)($input['surface'] ?? 'web')));
        $surface = in_array($surface, ['web', 'android'], true) ? $surface : 'web';
        $context_message_id = max(0, (int)($input['context_message_id'] ?? 0));

        if ($manager_id <= 0 || !$this->chatExists($chat_id)) return $this->fail('chat_not_found', 404);
        if (!preg_match('/^[A-Za-z0-9._:-]{16,80}$/D', $mutation_id)) return $this->fail('mutation_id_invalid', 422);
        if (mb_strlen($text, 'UTF-8') > self::MAX_TEXT_LENGTH) return $this->fail('draft_too_long', 422);
        if ($this->secret === '') return $this->fail('draft_storage_unavailable', 503);
        $reply_capability = (new ReplyCapability($this->registry))->forChatId($chat_id);
        if (trim($text) !== '' && empty($reply_capability['can_reply'])) {
            return $this->fail('draft_not_allowed', 409, ['reply_capability' => $reply_capability]);
        }

        $hash = hash_hmac('sha256', $manager_id . '|' . $chat_id . '|' . $text, $this->secret);
        try {
            $this->model->begin();
            $existing = $this->model->find($manager_id, $chat_id, self::SCOPE_REPLY, true);
            if ($existing && hash_equals((string)$existing['last_mutation_id'], $mutation_id)) {
                $this->model->commit();
                if (!hash_equals((string)$existing['content_hmac'], $hash)) return $this->fail('mutation_id_reused', 409);
                return ['success' => true, 'replayed' => true, 'operator_draft' => $this->payload($existing, $manager_id, $chat_id)];
            }
            if (!$existing && trim($text) === '') {
                 
                 
                $this->model->commit();
                return ['success' => true, 'replayed' => true, 'operator_draft' => $this->emptyPayload($chat_id)];
            }
            $current_revision = (int)($existing['revision'] ?? 0);
            if ($current_revision !== $base_revision) {
                $this->model->commit();
                return $this->fail('draft_conflict', 409, ['operator_draft' => $this->payload($existing, $manager_id, $chat_id)]);
            }
            if (trim($text) === '') {
                if ($existing) $this->model->deleteExact($manager_id, $chat_id, $current_revision);
                $this->model->commit();
                return ['success' => true, 'operator_draft' => $this->emptyPayload($chat_id)];
            }
            $ciphertext = $this->encrypt($text, $manager_id, $chat_id);
            $data = compact('manager_id', 'chat_id', 'ciphertext', 'mutation_id', 'context_message_id', 'surface');
            $data['content_hmac'] = $hash;
            $data['scope'] = self::SCOPE_REPLY;
            if ($existing) {
                if (!$this->model->update((int)$existing['draft_id'], $current_revision, $data)) {
                    throw new \RuntimeException('draft_compare_and_swap_failed');
                }
            } else {
                $this->model->insert($data);
            }
            $saved = $this->model->find($manager_id, $chat_id, self::SCOPE_REPLY, true);
            $this->model->commit();
            return ['success' => true, 'operator_draft' => $this->payload($saved, $manager_id, $chat_id)];
        } catch (\Throwable $e) {
            try { $this->model->rollback(); } catch (\Throwable $ignored) {}
            return $this->fail('draft_storage_failed', 503);
        }
    }

     
    public function clearAfterAccepted(int $manager_id, int $chat_id, int $revision): bool {
        if ($revision <= 0) return false;
        if ($this->model->deleteExact($manager_id, $chat_id, $revision, self::SCOPE_REPLY)) return true;
         
         
        return !$this->model->find($manager_id, $chat_id, self::SCOPE_REPLY);
    }

    private function payload(array $row, int $manager_id, int $chat_id): array {
        if (!$row) return $this->emptyPayload($chat_id);
        try {
            $text = $this->decrypt((string)$row['payload_ciphertext'], $manager_id, $chat_id);
        } catch (\Throwable $e) {
            return $this->emptyPayload($chat_id) + ['unavailable' => true];
        }
        if ($text === '') return $this->emptyPayload($chat_id);
        $max_message_id = isset($row['max_message_id'])
            ? (int)$row['max_message_id']
            : (int)(((new Message($this->registry))->getLastMessage($chat_id))['message_id'] ?? 0);
        return [
            'exists' => true,
            'chat_id' => $chat_id,
            'text' => $text,
            'preview' => $this->preview($text),
            'revision' => (int)$row['revision'],
            'context_message_id' => (int)$row['context_message_id'],
            'has_new_context' => $max_message_id > (int)$row['context_message_id'],
            'updated_at' => (string)$row['date_modified'],
            'surface' => (string)$row['surface'],
        ];
    }

    private function summary(array $payload): array {
        unset($payload['text']);
        return $payload;
    }

    private function emptyPayload(int $chat_id): array {
        return ['exists' => false, 'chat_id' => $chat_id, 'preview' => '', 'revision' => 0, 'context_message_id' => 0, 'has_new_context' => false, 'updated_at' => '', 'surface' => ''];
    }

    private function encrypt(string $text, int $manager_id, int $chat_id): string {
        $json = json_encode(['manager_id' => $manager_id, 'chat_id' => $chat_id, 'text' => $text], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        return SecretBox::encrypt((string)$json, $this->secret);
    }

    private function decrypt(string $ciphertext, int $manager_id, int $chat_id): string {
        if ($this->secret === '') throw new \RuntimeException('draft_storage_unavailable');
        $decoded = json_decode(SecretBox::decrypt($ciphertext, $this->secret), true);
        if (!is_array($decoded) || (int)($decoded['manager_id'] ?? 0) !== $manager_id || (int)($decoded['chat_id'] ?? 0) !== $chat_id) {
            throw new \RuntimeException('draft_integrity_failed');
        }
        return (string)($decoded['text'] ?? '');
    }

    private function preview(string $text): string {
        $text = preg_replace('/\s+/u', ' ', trim($text)) ?: '';
        return mb_strlen($text, 'UTF-8') > 120 ? mb_substr($text, 0, 119, 'UTF-8') . '…' : $text;
    }

    private function chatExists(int $chat_id): bool {
        return $chat_id > 0 && (bool)(new Session($this->registry))->getSessionById($chat_id);
    }

    private function fail(string $code, int $status, array $extra = []): array {
        return ['success' => false, 'code' => $code, 'error' => $code, 'status' => $status] + $extra;
    }
}
