<?php
class PortalController
{
    public function messages(): void
    {
        [$type, $id, $actor, $token] = $this->actor();
        View::render('portal/messages', [
            'actorType' => $type,
            'actorId' => $id,
            'actor' => $actor,
            'token' => $token,
            'conversationFolders' => Communication::conversationFolders($type, $id),
            'savedContacts' => Communication::savedContacts($type, $id),
            'groups' => TheoryGroup::forActor($type, $id),
            'unreadMessages' => Communication::unreadCount($type, $id),
            'adminMessages' => $type === 'admin' ? Communication::allRecentForAdmin() : [],
        ]);
    }

    public function direct(): void
    {
        [$type, $id, $actor, $token] = $this->actor();
        $contactType = normalize_internal_actor_type((string)($_GET['contact_type'] ?? 'student'));
        $contactId = (int)($_GET['contact_id'] ?? 0);
        $contact = Communication::actorByTypeAndId($contactType, $contactId);
        if (!$contact || ($contact['actor_type'] ?? '') !== $contactType || ($contactType === $type && $contactId === $id)
            || !Communication::canDirectContact($type, $id, $contactType, $contactId)) {
            flash('danger', 'Conversa não encontrada ou ainda não liberada por agendamento.');
            $this->redirectPortal('portal/messages', $token);
        }
        View::render('portal/direct', [
            'actorType' => $type,
            'actorId' => $id,
            'actor' => $actor,
            'token' => $token,
            'contact' => $contact,
            'messages' => Communication::conversationMessages($type, $id, $contactType, $contactId),
        ]);
    }

    public function contacts(): void
    {
        [$type, $id, $actor, $token] = $this->actor();
        $search = trim((string)($_GET['q'] ?? ''));
        View::render('portal/contacts', [
            'actorType' => $type,
            'actorId' => $id,
            'actor' => $actor,
            'token' => $token,
            'search' => $search,
            'contacts' => Communication::directory($type, $id, $search),
            'savedContacts' => Communication::savedContacts($type, $id),
        ]);
    }

    public function saveContact(): void
    {
        CSRF::check();
        [$type, $id, $actor, $token] = $this->actor(true);
        $result = Communication::saveContact(
            $type,
            $id,
            (string)($_POST['contact_type'] ?? ''),
            (int)($_POST['contact_id'] ?? 0)
        );
        flash($result['ok'] ? 'success' : 'danger', $result['message']);
        $this->redirectPortal('portal/contacts', $token, ['q' => trim((string)($_POST['q'] ?? ''))]);
    }

    public function deleteContact(): void
    {
        CSRF::check();
        [$type, $id, $actor, $token] = $this->actor(true);
        $ok = Communication::deleteContact(
            $type,
            $id,
            (string)($_POST['contact_type'] ?? ''),
            (int)($_POST['contact_id'] ?? 0)
        );
        flash($ok ? 'success' : 'danger', $ok ? 'Contato removido da sua lista.' : 'Contato não encontrado.');
        $this->redirectPortal('portal/contacts', $token, ['q' => trim((string)($_POST['q'] ?? ''))]);
    }

    public function sendMessage(): void
    {
        CSRF::check();
        [$type, $id, $actor, $token] = $this->actor(true);
        $recipientType = trim((string)($_POST['recipient_type'] ?? ''));
        $recipientId = (int)($_POST['recipient_id'] ?? 0);
        $audioFile = $_FILES['message_audio'] ?? [];
        $preparedAudio = [];
        $audioToken = trim((string)($_POST['message_audio_token'] ?? ''));
        if ($audioToken !== '') {
            $preparedAudio = PortalMedia::claimMessageAudio($audioToken, $type, $id);
            if (empty($preparedAudio['ok'])) {
                flash('danger', (string)($preparedAudio['message'] ?? 'O áudio enviado não pôde ser recuperado.'));
                $this->redirectPortal('portal/messages', $token);
            }
        }
        if ($recipientType !== '' && $recipientId > 0) {
            $result = Communication::sendDirectToActor($type, $id, $recipientType, $recipientId, (string)($_POST['message_body'] ?? ''), $audioFile, $preparedAudio);
        } else {
            $result = Communication::sendDirect($type, $id, (string)($_POST['recipient_code'] ?? ''), (string)($_POST['message_body'] ?? ''), $audioFile, $preparedAudio);
        }
        flash($result['ok'] ? 'success' : 'danger', $result['message']);
        if (!empty($result['ok']) && !empty($result['group_id'])) {
            $this->redirectPortal('portal/messages/group', $token, ['id' => (int)$result['group_id']]);
        }
        if (!empty($result['ok']) && !empty($result['recipient_type']) && !empty($result['recipient_id'])) {
            $this->redirectPortal('portal/messages/direct', $token, [
                'contact_type' => (string)$result['recipient_type'],
                'contact_id' => (int)$result['recipient_id'],
            ]);
        }
        $this->redirectPortal('portal/messages', $token);
    }

    public function uploadAudioChunk(): void
    {
        CSRF::check();
        [$type, $id] = $this->actor(true);
        $result = PortalMedia::storeMessageAudioChunk($_FILES['audio_chunk'] ?? [], $_POST, $type, $id);
        $this->jsonResponse($result, !empty($result['ok']) ? 200 : 422);
    }

    public function finalizeAudioUpload(): void
    {
        CSRF::check();
        [$type, $id] = $this->actor(true);
        $result = PortalMedia::finalizeMessageAudioChunks((string)($_POST['upload_id'] ?? ''), $type, $id);
        $this->jsonResponse($result, !empty($result['ok']) ? 200 : 422);
    }

    public function group(): void
    {
        [$type, $id, $actor, $token] = $this->actor();
        $groupId = (int)($_GET['id'] ?? 0);
        $group = TheoryGroup::find($groupId);
        if (!$group || !TheoryGroup::canAccess($groupId, $type, $id)) {
            flash('danger', 'Grupo não encontrado ou sem acesso.');
            $this->redirectPortal('portal/messages', $token);
        }
        View::render('portal/group', [
            'actorType' => $type,
            'actorId' => $id,
            'actor' => $actor,
            'token' => $token,
            'group' => $group,
            'members' => TheoryGroup::members($groupId),
            'messages' => Communication::groupMessages($groupId, $type, $id),
            'groupVideos' => VideoLesson::forGroup($groupId),
        ]);
    }

    public function sendGroupMessage(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST' && empty($_POST) && empty($_FILES)) {
            [$type, $id, $actor, $token] = $this->actor(false);
            $groupId = (int)($_GET['id'] ?? 0);
            flash('danger', 'O arquivo ultrapassou o limite da hospedagem. Envie áudio, imagem ou PDF de até 25 MB.');
            $this->redirectPortal('portal/messages/group', $token, ['id' => $groupId]);
        }
        CSRF::check();
        [$type, $id, $actor, $token] = $this->actor(true);
        $groupId = (int)($_POST['group_id'] ?? 0);
        $attachment = $_FILES['message_audio'] ?? [];
        $preparedAudio = [];
        $audioToken = trim((string)($_POST['message_audio_token'] ?? ''));
        if ($audioToken !== '') {
            $preparedAudio = PortalMedia::claimMessageAudio($audioToken, $type, $id);
            if (empty($preparedAudio['ok'])) {
                flash('danger', (string)($preparedAudio['message'] ?? 'O áudio enviado não pôde ser recuperado.'));
                $this->redirectPortal('portal/messages/group', $token, ['id' => $groupId]);
            }
        }
        if (empty($preparedAudio['path']) && empty($attachment['tmp_name']) && (int)($attachment['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
            $attachment = $_FILES['group_attachment'] ?? [];
        }
        $result = Communication::sendGroup($type, $id, $groupId, (string)($_POST['message_body'] ?? ''), $attachment, $preparedAudio);
        flash($result['ok'] ? 'success' : 'danger', $result['message']);
        $this->redirectPortal('portal/messages/group', $token, ['id' => $groupId]);
    }

    public function messageFile(): void
    {
        [$type, $id] = $this->actor();
        $messageId = (int)($_GET['message_id'] ?? 0);
        $message = Communication::attachmentForActor($messageId, $type, $id);
        if (!$message) {
            http_response_code(404);
            exit('Arquivo não encontrado ou sem acesso.');
        }
        $relative = trim((string)$message['attachment_path']);
        $absolute = ROOT_PATH . '/' . ltrim($relative, '/');
        $realRoot = realpath(UPLOAD_PATH);
        $realPath = realpath($absolute);
        if (!$realRoot || !$realPath || !str_starts_with($realPath, $realRoot . DIRECTORY_SEPARATOR) || !is_file($realPath)) {
            http_response_code(404);
            exit('Arquivo não encontrado.');
        }
        while (ob_get_level() > 0) {
            ob_end_clean();
        }
        $mime = trim((string)($message['attachment_mime'] ?? '')) ?: 'application/octet-stream';
        $name = str_replace([chr(34), chr(13), chr(10)], '', basename((string)($message['attachment_name'] ?? 'arquivo')));
        $inline = str_starts_with($mime, 'image/') || str_starts_with($mime, 'audio/')
            || in_array($mime, ['video/webm', 'application/ogg', 'application/pdf'], true);
        header('Content-Type: ' . $mime);
        header('Content-Length: ' . filesize($realPath));
        header('Content-Disposition: ' . ($inline ? 'inline' : 'attachment') . '; filename="' . $name . '"');
        header('X-Content-Type-Options: nosniff');
        header('Accept-Ranges: bytes');
        header('Cache-Control: private, max-age=300');
        readfile($realPath);
        exit;
    }

    public function groupFile(): void
    {
        $this->messageFile();
    }

    public function deleteMessage(): void
    {
        CSRF::check();
        [$type, $id, $actor, $token] = $this->actor(true);
        $messageId = (int)($_POST['message_id'] ?? 0);
        $forAll = ($_POST['delete_mode'] ?? 'self') === 'all';
        $result = $forAll ? Communication::deleteForAll($messageId, $type, $id) : Communication::deleteForSelf($messageId, $type, $id);
        flash($result['ok'] ? 'success' : 'danger', $result['message']);
        $professionalGroupId = (int)($_POST['professional_group_id'] ?? 0);
        if ($professionalGroupId > 0 && $type === 'professional' && ProfessionalChatGroup::canAccess($professionalGroupId, $id)) {
            $params = ['id' => $professionalGroupId];
            if ($token !== '') { $params['token'] = $token; }
            redirect('professional/chat-groups', $params);
        }
        $groupId = (int)($_POST['group_id'] ?? 0);
        if ($groupId > 0) {
            $this->redirectPortal('portal/messages/group', $token, ['id' => $groupId]);
        }
        $contactType = trim((string)($_POST['contact_type'] ?? ''));
        $contactId = (int)($_POST['contact_id'] ?? 0);
        if ($contactType !== '' && $contactId > 0) {
            $this->redirectPortal('portal/messages/direct', $token, ['contact_type' => $contactType, 'contact_id' => $contactId]);
        }
        $this->redirectPortal('portal/messages', $token);
    }

    public function videos(): void
    {
        [$type, $id, $actor, $token] = $this->actor();
        $videos = $type === 'admin' ? VideoLesson::all() : VideoLesson::forActor($type, $id);
        View::render('portal/videos', ['actorType' => $type, 'actorId' => $id, 'actor' => $actor, 'token' => $token, 'videos' => $videos]);
    }

    public function streamVideo(): void
    {
        [$type, $id] = $this->actor();
        $lessonId = (int)($_GET['id'] ?? 0);
        if ($type === 'admin') {
            $lesson = null;
            foreach (VideoLesson::all() as $item) {
                if ((int)$item['id'] === $lessonId) {
                    $lesson = $item;
                    break;
                }
            }
        } else {
            $lesson = VideoLesson::findForActor($lessonId, $type, $id);
        }
        if (!$lesson || ($lesson['source_type'] ?? '') !== 'upload') {
            http_response_code(404);
            exit('Videoaula não encontrada ou sem acesso.');
        }
        VideoLesson::stream($lesson);
    }

    public function payments(): void
    {
        [$type, $id, $actor, $token] = $this->actor();
        if ($type === 'professional') { flash('info', 'A área de bancos e cobranças não faz parte do painel do instrutor.'); redirect('professional/panel', $token !== '' ? ['token'=>$token] : []); }
        $charges = in_array($type, ['student', 'beginner'], true) ? PaymentCenter::forActor($type, $id) : [];
        $receipts = in_array($type, ['student', 'beginner'], true) ? PaymentReceipt::forActor($type, $id) : [];
        View::render('portal/payments', ['actorType' => $type, 'actorId' => $id, 'actor' => $actor, 'token' => $token, 'charges' => $charges, 'receipts' => $receipts]);
    }

    public function uploadPaymentProof(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST' && empty($_POST) && empty($_FILES)) {
            [$type, $id, $actor, $token] = $this->actor(false);
            flash('danger', 'O comprovante ultrapassou o limite de upload da hospedagem. Envie imagem ou PDF de até 10 MB.');
            $this->redirectPortal('portal/payments', $token);
        }
        CSRF::check();
        [$type, $id, $actor, $token] = $this->actor(true);
        $result = PaymentCenter::submitProof((int)($_POST['charge_id'] ?? 0), $type, $id, $_FILES['payment_proof'] ?? []);
        flash($result['ok'] ? 'success' : 'danger', $result['message']);
        $this->redirectPortal('portal/payments', $token);
    }

    public function paymentProofFile(): void
    {
        [$type, $id] = $this->actor();
        $charge = PaymentCenter::proofForActor((int)($_GET['charge_id'] ?? 0), $type, $id);
        if (!$charge) {
            http_response_code(404);
            exit('Comprovante não encontrado ou sem acesso.');
        }
        $relative = trim((string)($charge['proof_path'] ?? ''));
        $absolute = ROOT_PATH . '/' . ltrim($relative, '/');
        $realRoot = realpath(UPLOAD_PATH);
        $realPath = realpath($absolute);
        if (!$realRoot || !$realPath || !str_starts_with($realPath, $realRoot . DIRECTORY_SEPARATOR) || !is_file($realPath)) {
            http_response_code(404);
            exit('Arquivo do comprovante não encontrado.');
        }
        while (ob_get_level() > 0) ob_end_clean();
        $mime = trim((string)($charge['proof_mime'] ?? '')) ?: 'application/octet-stream';
        $name = str_replace([chr(34), chr(13), chr(10)], '', basename((string)($charge['proof_name'] ?? 'comprovante')));
        header('Content-Type: ' . $mime);
        header('Content-Length: ' . filesize($realPath));
        header('Content-Disposition: inline; filename="' . $name . '"');
        header('X-Content-Type-Options: nosniff');
        header('Cache-Control: private, max-age=300');
        readfile($realPath);
        exit;
    }

    private function jsonResponse(array $payload, int $status = 200): never
    {
        while (ob_get_level() > 0) {
            ob_end_clean();
        }
        http_response_code($status);
        header('Content-Type: application/json; charset=utf-8');
        header('Cache-Control: no-store, no-cache, must-revalidate');
        echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }

    private function actor(bool $fromPost = false): array
    {
        Database::migrate();
        $user = Auth::user();
        if ($user) {
            $type = ($user['role'] ?? '') === 'admin' ? 'admin' : 'student';
            return [$type, (int)$user['id'], $user, ''];
        }
        $token = trim((string)(($fromPost ? ($_POST['token'] ?? '') : ($_GET['token'] ?? ''))));
        if ($token !== '') {
            $professional = Professional::findByToken($token);
            if ($professional) {
                return ['professional', (int)$professional['id'], $professional, $token];
            }
        }
        $professional = Professional::current();
        if ($professional) {
            return ['professional', (int)$professional['id'], $professional, ''];
        }
        $beginner = BeginnerInstructor::current();
        if ($beginner) {
            return ['beginner', (int)$beginner['id'], $beginner, ''];
        }
        flash('danger', 'Entre em sua conta para acessar esta área.');
        redirect('home');
    }

    private function redirectPortal(string $route, string $token, array $params = []): never
    {
        if ($token !== '') {
            $params['token'] = $token;
        }
        redirect($route, array_filter($params, static fn($value): bool => $value !== '' && $value !== null));
    }
}
