<?php

namespace Modules\Messaging\Services;

use App\Models\ActivityLogModel;
use App\Models\MessageConversationModel;
use App\Models\MessageModel;
use App\Models\MessageParticipantModel;
use Modules\Auth\Models\UserModel;

class MessagingService
{
    protected MessageConversationModel $conversations;
    protected MessageParticipantModel $participants;
    protected MessageModel $messages;
    protected UserModel $users;
    protected ActivityLogModel $activity;

    public function __construct()
    {
        $this->conversations = new MessageConversationModel();
        $this->participants  = new MessageParticipantModel();
        $this->messages      = new MessageModel();
        $this->users         = new UserModel();
        $this->activity      = new ActivityLogModel();
    }

    public function userCanAccess(array $conversation, int $userId): bool
    {
        if (($conversation['type'] ?? '') === 'public') {
            return true;
        }

        return $this->participants->isMember((int) $conversation['id'], $userId);
    }

    /**
     * @return array{success: bool, id?: int, errors?: array, error?: string}
     */
    public function startDirect(int $currentUserId, int $peerUserId): array
    {
        if ($peerUserId <= 0 || $peerUserId === $currentUserId) {
            return ['success' => false, 'errors' => ['peer_user_id' => 'Choose another user to message.']];
        }

        $peer = $this->users->find($peerUserId);
        if (! $peer || ($peer->status ?? '') !== 'active') {
            return ['success' => false, 'error' => 'User not found or inactive.'];
        }

        $existing = $this->conversations->findDirectBetween($currentUserId, $peerUserId);
        if ($existing) {
            return ['success' => true, 'id' => (int) $existing['id']];
        }

        $id = $this->conversations->insert([
            'type'       => 'direct',
            'name'       => null,
            'created_by' => $currentUserId,
            'is_system'  => 0,
        ], true);

        if (! $id) {
            return ['success' => false, 'error' => 'Could not start conversation.'];
        }

        $this->participants->addMember((int) $id, $currentUserId);
        $this->participants->addMember((int) $id, $peerUserId);

        $this->activity->log('messaging', 'dm_start', $currentUserId, 'Started direct conversation', (string) $id);

        return ['success' => true, 'id' => (int) $id];
    }

    /**
     * @param array<string, mixed> $data
     * @return array{success: bool, id?: int, errors?: array, error?: string}
     */
    public function createGroup(array $data, int $adminUserId): array
    {
        $name = trim((string) ($data['name'] ?? ''));
        $description = trim((string) ($data['description'] ?? ''));
        $memberIds = $data['member_ids'] ?? [];

        if (! is_array($memberIds)) {
            $memberIds = [];
        }

        $errors = [];
        if ($name === '' || strlen($name) < 2) {
            $errors['name'] = 'Group name is required (at least 2 characters).';
        }
        if (strlen($name) > 150) {
            $errors['name'] = 'Group name is too long.';
        }
        if (strlen($description) > 500) {
            $errors['description'] = 'Description is too long.';
        }

        if ($errors) {
            return ['success' => false, 'errors' => $errors];
        }

        $id = $this->conversations->insert([
            'type'        => 'group',
            'name'        => $name,
            'description' => $description !== '' ? $description : null,
            'created_by'  => $adminUserId,
            'is_system'   => 0,
        ], true);

        if (! $id) {
            return ['success' => false, 'error' => 'Could not create group.'];
        }

        $memberIds = array_map('intval', $memberIds);
        if (! in_array($adminUserId, $memberIds, true)) {
            $memberIds[] = $adminUserId;
        }

        $this->participants->syncMembers((int) $id, $memberIds);

        $this->activity->log('messaging', 'group_create', $adminUserId, 'Created group: ' . $name, (string) $id);

        return ['success' => true, 'id' => (int) $id];
    }

    /**
     * @param array<string, mixed> $data
     * @return array{success: bool, errors?: array, error?: string}
     */
    public function updateGroup(int $conversationId, array $data, int $adminUserId): array
    {
        $conversation = $this->conversations->find($conversationId);
        if (! $conversation || $conversation['type'] !== 'group') {
            return ['success' => false, 'error' => 'Group not found.'];
        }

        $name = trim((string) ($data['name'] ?? ''));
        $description = trim((string) ($data['description'] ?? ''));
        $memberIds = $data['member_ids'] ?? [];

        if (! is_array($memberIds)) {
            $memberIds = [];
        }

        $errors = [];
        if ($name === '' || strlen($name) < 2) {
            $errors['name'] = 'Group name is required (at least 2 characters).';
        }

        if ($errors) {
            return ['success' => false, 'errors' => $errors];
        }

        $this->conversations->update($conversationId, [
            'name'        => $name,
            'description' => $description !== '' ? $description : null,
        ]);

        $memberIds = array_map('intval', $memberIds);
        $this->participants->syncMembers($conversationId, $memberIds);

        $this->activity->log('messaging', 'group_update', $adminUserId, 'Updated group: ' . $name, (string) $conversationId);

        return ['success' => true];
    }

    /**
     * @return array{success: bool, error?: string}
     */
    public function deleteGroup(int $conversationId, int $adminUserId): array
    {
        $conversation = $this->conversations->find($conversationId);
        if (! $conversation || $conversation['type'] !== 'group') {
            return ['success' => false, 'error' => 'Group not found.'];
        }

        if ((int) ($conversation['is_system'] ?? 0) === 1) {
            return ['success' => false, 'error' => 'System channels cannot be deleted.'];
        }

        $db = \Config\Database::connect();
        $db->transStart();
        $db->table('messages')->where('conversation_id', $conversationId)->delete();
        $db->table('message_participants')->where('conversation_id', $conversationId)->delete();
        $db->table('message_conversations')->where('id', $conversationId)->delete();
        $db->transComplete();

        if (! $db->transStatus()) {
            return ['success' => false, 'error' => 'Could not delete group.'];
        }

        $this->activity->log('messaging', 'group_delete', $adminUserId, 'Deleted group: ' . ($conversation['name'] ?? ''), (string) $conversationId);

        return ['success' => true];
    }

    /**
     * @return array{success: bool, message?: array, errors?: array, error?: string}
     */
    public function send(int $conversationId, int $senderId, string $body): array
    {
        $conversation = $this->conversations->find($conversationId);
        if (! $conversation) {
            return ['success' => false, 'error' => 'Conversation not found.'];
        }

        if (! $this->userCanAccess($conversation, $senderId)) {
            return ['success' => false, 'error' => 'You are not a member of this conversation.'];
        }

        $body = trim($body);
        if ($body === '') {
            return ['success' => false, 'errors' => ['body' => 'Message cannot be empty.']];
        }
        if (mb_strlen($body) > 5000) {
            return ['success' => false, 'errors' => ['body' => 'Message is too long.']];
        }

        $id = $this->messages->insert([
            'conversation_id' => $conversationId,
            'sender_id'       => $senderId,
            'body'            => $body,
        ], true);

        if (! $id) {
            return ['success' => false, 'error' => 'Could not send message.', 'errors' => $this->messages->errors()];
        }

        $this->conversations->update($conversationId, ['updated_at' => date('Y-m-d H:i:s')]);
        $this->participants->markRead($conversationId, $senderId);

        $message = $this->messages->select('messages.*, users.first_name, users.last_name, users.username')
            ->join('users', 'users.id = messages.sender_id', 'left')
            ->find($id);

        return ['success' => true, 'message' => $message];
    }

    /**
     * Active users available for DMs / group membership (excluding optional self).
     *
     * @return list<array<string, mixed>>
     */
    public function listActiveUsers(?int $excludeUserId = null): array
    {
        $builder = $this->users
            ->select('id, first_name, last_name, username, email')
            ->where('status', 'active')
            ->orderBy('first_name', 'ASC')
            ->orderBy('last_name', 'ASC');

        if ($excludeUserId) {
            $builder->where('id !=', $excludeUserId);
        }

        $rows = $builder->findAll();

        // UserModel returns entities — normalize to arrays for views
        return array_map(static function ($row) {
            if (is_object($row) && method_exists($row, 'toArray')) {
                return $row->toArray();
            }
            if (is_object($row)) {
                return (array) $row;
            }

            return $row;
        }, $rows);
    }

    public function displayTitle(array $conversation, int $currentUserId): string
    {
        if (($conversation['type'] ?? '') === 'direct') {
            if (! empty($conversation['peer_name'])) {
                return (string) $conversation['peer_name'];
            }

            $members = $this->participants->getMembersWithNames((int) $conversation['id']);
            foreach ($members as $m) {
                if ((int) $m['user_id'] !== $currentUserId) {
                    return trim(($m['first_name'] ?? '') . ' ' . ($m['last_name'] ?? '')) ?: ($m['username'] ?? 'Direct message');
                }
            }

            return 'Direct message';
        }

        return (string) ($conversation['name'] ?? 'Conversation');
    }
}
