import { createHash } from "node:crypto"
import { FastifyInstance } from "fastify"
import multipart from "@fastify/multipart"
import { and, desc, eq, inArray, ne } from "drizzle-orm"
import { authProfiles, authTenantUsers, authUsers, notificationsItems, projects } from "../../db/schema"
import { matrixService } from "../modules/matrix.service"
import { getMatrixPushWorkerState } from "../modules/matrix-push-worker.service"
import { NotificationService, UserDirectory } from "../modules/notification.service"

const getUserDirectory: UserDirectory = async (server: FastifyInstance, userId) => {
    const rows = await server.db
        .select({ email: authUsers.email })
        .from(authUsers)
        .where(eq(authUsers.id, userId))
        .limit(1)

    return rows[0] || null
}

type ChatRecipient = {
    userId: string
    email?: string | null
    firstName?: string | null
    lastName?: string | null
    fullName?: string | null
}

export default async function communicationRoutes(server: FastifyInstance) {
    await server.register(multipart, {
        limits: { fileSize: 25 * 1024 * 1024 },
    })

    const matrix = matrixService(server)
    const notifications = new NotificationService(server, getUserDirectory)
    const handleMatrixError = (req: any, reply: any, err: any, fallbackMessage: string) => {
        req.log.error(err)
        return reply
            .code(err.statusCode || 500)
            .send({ error: err.message || fallbackMessage })
    }

    const roomOptionsFromRequest = (req: any) => {
        const params = req.params as { roomKey?: string }
        const body = (req.body || {}) as {
            key?: string
            name?: string
            topic?: string
            type?: string
            entityType?: string | null
            entityId?: number | null
            entityUuid?: string | null
        }

        return {
            key: params.roomKey || body.key,
            name: body.name,
            topic: body.topic,
            type: body.type,
            entityType: body.entityType,
            entityId: body.entityId,
            entityUuid: body.entityUuid,
        }
    }

    const projectRoomKey = (projectId: number) => `project_${projectId}`

    const directRoomKey = (firstUserId: string, secondUserId: string) => {
        const hash = createHash("sha256")
            .update([firstUserId, secondUserId].sort().join(":"))
            .digest("hex")
            .slice(0, 16)

        return `direct_${hash}`
    }

    const displayUserName = (user: { fullName?: string | null; firstName?: string | null; lastName?: string | null; email?: string | null }) => {
        const name = user.fullName || [user.firstName, user.lastName].filter(Boolean).join(" ")
        return name || user.email || "Benutzer"
    }

    const getTenantRecipients = async (tenantId: number, senderUserId: string) => {
        return await server.db
            .select({
                userId: authTenantUsers.user_id,
                email: authUsers.email,
                firstName: authProfiles.first_name,
                lastName: authProfiles.last_name,
                fullName: authProfiles.full_name,
            })
            .from(authTenantUsers)
            .innerJoin(authUsers, eq(authUsers.id, authTenantUsers.user_id))
            .leftJoin(authProfiles, and(
                eq(authProfiles.user_id, authTenantUsers.user_id),
                eq(authProfiles.tenant_id, tenantId)
            ))
            .where(and(
                eq(authTenantUsers.tenant_id, tenantId),
                ne(authTenantUsers.user_id, senderUserId)
            ))
    }

    const getTenantUser = async (tenantId: number, userId: string): Promise<ChatRecipient | null> => {
        const [user] = await server.db
            .select({
                userId: authTenantUsers.user_id,
                email: authUsers.email,
                firstName: authProfiles.first_name,
                lastName: authProfiles.last_name,
                fullName: authProfiles.full_name,
            })
            .from(authTenantUsers)
            .innerJoin(authUsers, eq(authUsers.id, authTenantUsers.user_id))
            .leftJoin(authProfiles, and(
                eq(authProfiles.user_id, authTenantUsers.user_id),
                eq(authProfiles.tenant_id, tenantId)
            ))
            .where(and(
                eq(authTenantUsers.tenant_id, tenantId),
                eq(authTenantUsers.user_id, userId)
            ))
            .limit(1)

        return user || null
    }

    const getSenderName = async (tenantId: number, senderUserId: string) => {
        const [sender] = await server.db
            .select({
                email: authUsers.email,
                firstName: authProfiles.first_name,
                lastName: authProfiles.last_name,
                fullName: authProfiles.full_name,
            })
            .from(authUsers)
            .leftJoin(authProfiles, and(
                eq(authProfiles.user_id, authUsers.id),
                eq(authProfiles.tenant_id, tenantId)
            ))
            .where(eq(authUsers.id, senderUserId))
            .limit(1)

        return sender ? displayUserName(sender) : "FEDEO"
    }

    const mentionAliasesForUser = (user: ChatRecipient) => {
        const name = displayUserName(user)
        return Array.from(new Set([
            name,
            user.fullName,
            [user.firstName, user.lastName].filter(Boolean).join(" "),
            user.firstName,
            user.email,
        ].filter(Boolean).map((value) => String(value).toLowerCase())))
    }

    const mentionedRecipientIds = (text: string, recipients: ChatRecipient[]) => {
        const normalizedText = text.toLowerCase()

        return recipients
            .filter((recipient) => mentionAliasesForUser(recipient).some((alias) =>
                normalizedText.includes(`@${alias}`)
            ))
            .map((recipient) => recipient.userId)
    }

    const chatMessageRecipients = async (
        tenantId: number,
        senderUserId: string,
        room: any,
        text: string
    ) => {
        const recipients = await getTenantRecipients(tenantId, senderUserId)
        const mentioned = new Set(mentionedRecipientIds(text, recipients))
        const directRecipients = new Set<string>()

        if (room?.type === "direct" && room.entityUuid && room.entityUuid !== senderUserId) {
            directRecipients.add(room.entityUuid)
        } else if (room?.type === "direct" && room.key) {
            recipients
                .filter((recipient) => directRoomKey(senderUserId, recipient.userId) === room.key)
                .forEach((recipient) => directRecipients.add(recipient.userId))
        }

        return recipients
            .filter((recipient) => directRecipients.has(recipient.userId) || mentioned.has(recipient.userId))
            .map((recipient) => ({
                ...recipient,
                mentioned: mentioned.has(recipient.userId),
                direct: directRecipients.has(recipient.userId),
            }))
    }

    const notifyUsersAboutChatMessage = async (req: any, room: any, message: any, text: string) => {
        if (!req.user.tenant_id) return

        try {
            const recipients = await chatMessageRecipients(req.user.tenant_id, req.user.user_id, room, text)
            if (!recipients.length) return

            const senderName = await getSenderName(req.user.tenant_id, req.user.user_id)
            const preview = text.length > 160 ? `${text.slice(0, 157)}...` : text

            for (const recipient of recipients) {
                await notifications.trigger({
                    tenantId: req.user.tenant_id,
                    userId: recipient.userId,
                    eventType: "communication.message.new",
                    title: recipient.mentioned ? `${senderName} hat dich erwähnt` : `Neue Direktnachricht von ${senderName}`,
                    message: preview,
                    payload: {
                        link: `/communication/chat?room=${encodeURIComponent(room.key)}`,
                        roomKey: room.key,
                        roomName: room.name,
                        roomType: room.type,
                        messageId: message.id,
                        mentioned: recipient.mentioned,
                        direct: recipient.direct,
                    },
                    channels: ["inapp", "push"],
                })
            }
        } catch (err) {
            req.log.error({ err }, "Chat-Benachrichtigung konnte nicht ausgelöst werden")
        }
    }

    const hasChatNotificationForMessage = async (tenantId: number, userId: string, messageId: string) => {
        const rows = await server.db
            .select({
                payload: notificationsItems.payload,
            })
            .from(notificationsItems)
            .where(and(
                eq(notificationsItems.tenantId, tenantId),
                eq(notificationsItems.userId, userId),
                eq(notificationsItems.eventType, "communication.message.new")
            ))
            .orderBy(desc(notificationsItems.createdAt))
            .limit(200)

        return rows.some((row) => (row.payload as any)?.messageId === messageId)
    }

    const notifyCurrentUserAboutIncomingMatrixMessages = async (req: any, room: any, messages: any[]) => {
        if (!req.user.tenant_id || !messages.length) return

        try {
            const currentUser = await getTenantUser(req.user.tenant_id, req.user.user_id)
            if (!currentUser) return

            for (const message of messages) {
                if (message.own || !message.id) continue

                const text = message.body || message.attachment?.fileName || "Neue Nachricht"
                const mentioned = mentionedRecipientIds(text, [currentUser]).includes(currentUser.userId)
                const direct = room?.type === "direct"

                if (!direct && !mentioned) continue
                if (await hasChatNotificationForMessage(req.user.tenant_id, req.user.user_id, message.id)) continue

                const senderName = message.senderDisplayName || message.sender || "Matrix"
                const preview = text.length > 160 ? `${text.slice(0, 157)}...` : text

                await notifications.trigger({
                    tenantId: req.user.tenant_id,
                    userId: req.user.user_id,
                    eventType: "communication.message.new",
                    title: mentioned ? `${senderName} hat dich erwähnt` : `Neue Direktnachricht von ${senderName}`,
                    message: preview,
                    payload: {
                        link: `/communication/chat?room=${encodeURIComponent(room.key)}`,
                        roomKey: room.key,
                        roomName: room.name,
                        roomType: room.type,
                        messageId: message.id,
                        matrixSender: message.sender,
                        mentioned,
                        direct,
                    },
                    channels: ["inapp", "push"],
                })
            }
        } catch (err) {
            req.log.error({ err }, "Eingehende Matrix-Benachrichtigung konnte nicht ausgelöst werden")
        }
    }

    const unreadChatNotifications = async (tenantId: number, userId: string) => {
        return await server.db
            .select({
                id: notificationsItems.id,
                payload: notificationsItems.payload,
            })
            .from(notificationsItems)
            .where(and(
                eq(notificationsItems.tenantId, tenantId),
                eq(notificationsItems.userId, userId),
                eq(notificationsItems.eventType, "communication.message.new"),
                eq(notificationsItems.channel, "inapp"),
                ne(notificationsItems.status, "read")
            ))
    }

    const markRoomNotificationsRead = async (tenantId: number, userId: string, roomKey: string) => {
        const rows = await unreadChatNotifications(tenantId, userId)
        const ids = rows
            .filter((row) => (row.payload as any)?.roomKey === roomKey)
            .map((row) => row.id)

        if (!ids.length) return { read: 0 }

        await server.db
            .update(notificationsItems)
            .set({ readAt: new Date(), status: "read" })
            .where(and(
                eq(notificationsItems.tenantId, tenantId),
                eq(notificationsItems.userId, userId),
                inArray(notificationsItems.id, ids)
            ))

        return { read: ids.length }
    }

    const uploadedAttachmentFromRequest = async (req: any) => {
        const data = await req.file()
        if (!data?.file) {
            throw Object.assign(
                new Error("Keine Datei hochgeladen"),
                { statusCode: 400 }
            )
        }

        const buffer = await data.toBuffer()
        return {
            buffer,
            filename: data.filename || "Anhang",
            mimeType: data.mimetype || "application/octet-stream",
            size: buffer.length,
        }
    }

    const callModeFromRequest = (req: any): "audio" | "video" => {
        const body = (req.body || {}) as { mode?: string }
        return body.mode === "audio" ? "audio" : "video"
    }

    const notifyTenantUsersAboutCall = async (req: any, room: { key?: string; name?: string }, mode: "audio" | "video") => {
        if (!req.user.tenant_id) return

        try {
            const recipientRows = await server.db
                .select({ userId: authTenantUsers.user_id })
                .from(authTenantUsers)
                .where(and(
                    eq(authTenantUsers.tenant_id, req.user.tenant_id),
                    ne(authTenantUsers.user_id, req.user.user_id)
                ))

            const userIds = recipientRows.map((row) => row.userId)
            if (!userIds.length) return

            await notifications.trigger({
                tenantId: req.user.tenant_id,
                userIds,
                eventType: "communication.call.started",
                title: mode === "audio" ? "Audioanruf gestartet" : "Videokonferenz gestartet",
                message: `${room.name || room.key || "Ein Chatraum"} hat eine laufende Besprechung.`,
                payload: {
                    link: "/communication/chat",
                    roomKey: room.key,
                    roomName: room.name,
                    mode,
                },
                channels: ["inapp", "push"],
            })
        } catch (err) {
            req.log.error({ err }, "Call-Benachrichtigung konnte nicht ausgelöst werden")
        }
    }

    server.get("/communication/matrix/status", async () => {
        return matrix.getStatus()
    })

    server.get("/communication/matrix/me", async (req) => {
        const userId = req.user.user_id

        return {
            matrixUserId: await matrix.matrixUserIdForUser(userId, req.user.tenant_id),
            displayName: await matrix.getCurrentUserDisplayName(userId, req.user.tenant_id),
        }
    })

    server.post("/communication/matrix/me/provision", async (req, reply) => {
        try {
            return await matrix.provisionCurrentUser(req.user.user_id, req.user.tenant_id)
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix provisioning failed")
        }
    })

    server.get("/communication/matrix/tenant-space", async (req, reply) => {
        try {
            return await matrix.getTenantSpaceStatus(req.user.tenant_id)
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix tenant space status failed")
        }
    })

    server.post("/communication/matrix/tenant-space/provision", async (req, reply) => {
        try {
            return await matrix.provisionCurrentTenantSpace(req.user.user_id, req.user.tenant_id)
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix tenant space provisioning failed")
        }
    })

    server.get("/communication/matrix/rooms", async (req, reply) => {
        try {
            return await matrix.listTenantRooms(req.user.tenant_id)
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix rooms failed")
        }
    })

    server.get("/communication/matrix/unread", async (req, reply) => {
        try {
            if (!req.user.tenant_id) return reply.code(400).send({ error: "Kein aktiver Mandant" })

            const rows = await unreadChatNotifications(req.user.tenant_id, req.user.user_id)
            const rooms = rows.reduce((acc: Record<string, { count: number; mentions: number }>, row) => {
                const payload = row.payload as any
                const roomKey = payload?.roomKey
                if (!roomKey) return acc

                acc[roomKey] = acc[roomKey] || { count: 0, mentions: 0 }
                acc[roomKey].count += 1

                if (payload.mentioned) {
                    acc[roomKey].mentions += 1
                }

                return acc
            }, {})

            return { rooms }
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix unread state failed")
        }
    })

    server.get("/communication/matrix/push-worker", async () => {
        return getMatrixPushWorkerState()
    })

    server.get("/communication/matrix/media", async (req, reply) => {
        try {
            const query = req.query as { uri?: string; name?: string }
            if (!query.uri) return reply.code(400).send({ error: "Matrix-Media-URI fehlt" })

            const media = await matrix.getMediaContent(req.user.user_id, req.user.tenant_id, query.uri)
            reply.header("Content-Type", media.contentType)
            if (media.contentLength) reply.header("Content-Length", media.contentLength)
            if (query.name) {
                reply.header("Content-Disposition", `inline; filename="${query.name.replace(/"/g, "")}"`)
            }
            return reply.send(media.buffer)
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix media failed")
        }
    })

    server.get("/communication/matrix/project-rooms", async (req, reply) => {
        try {
            if (!req.user.tenant_id) return reply.code(400).send({ error: "Kein aktiver Mandant" })

            const [roomsRes, projectRows] = await Promise.all([
                matrix.listTenantRooms(req.user.tenant_id),
                server.db
                    .select({
                        id: projects.id,
                        name: projects.name,
                        projectNumber: projects.projectNumber,
                        profiles: projects.profiles,
                    })
                    .from(projects)
                    .where(and(
                        eq(projects.tenant, req.user.tenant_id),
                        eq(projects.archived, false)
                    ))
            ])
            const roomsByKey = new Map((roomsRes.rooms || []).map((room: any) => [room.key, room]))

            return {
                rooms: projectRows.map((project) => {
                    const key = projectRoomKey(project.id)
                    const existing = roomsByKey.get(key) as any
                    return {
                        ...(existing || {}),
                        key,
                        name: project.projectNumber ? `${project.projectNumber} · ${project.name}` : project.name,
                        topic: `Projektkommunikation zu ${project.name}`,
                        type: "project",
                        entityType: "project",
                        entityId: project.id,
                        exists: Boolean(existing?.exists),
                        projectId: project.id,
                        projectNumber: project.projectNumber,
                    }
                })
            }
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix project rooms failed")
        }
    })

    server.post("/communication/matrix/project-rooms/:projectId/provision", async (req, reply) => {
        try {
            if (!req.user.tenant_id) return reply.code(400).send({ error: "Kein aktiver Mandant" })
            const params = req.params as { projectId: string }
            const projectId = Number(params.projectId)
            const [project] = await server.db
                .select()
                .from(projects)
                .where(and(
                    eq(projects.tenant, req.user.tenant_id),
                    eq(projects.id, projectId)
                ))
                .limit(1)

            if (!project) return reply.code(404).send({ error: "Projekt nicht gefunden" })

            const profileIds = (project.profiles || []) as string[]
            const profileRows = profileIds.length
                ? await server.db
                    .select({ userId: authProfiles.user_id })
                    .from(authProfiles)
                    .where(and(
                        eq(authProfiles.tenant_id, req.user.tenant_id),
                        inArray(authProfiles.id, profileIds)
                    ))
                : []
            const inviteUserIds = profileRows.map((profile) => profile.userId).filter(Boolean) as string[]

            return await matrix.provisionTenantRoom(req.user.user_id, req.user.tenant_id, {
                key: projectRoomKey(project.id),
                name: project.projectNumber ? `${project.projectNumber} · ${project.name}` : project.name,
                topic: `Projektkommunikation zu ${project.name}`,
                type: "project",
                entityType: "project",
                entityId: project.id,
                inviteUserIds,
            })
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix project room provisioning failed")
        }
    })

    server.get("/communication/matrix/direct-rooms", async (req, reply) => {
        try {
            if (!req.user.tenant_id) return reply.code(400).send({ error: "Kein aktiver Mandant" })
            const [roomsRes, userRows] = await Promise.all([
                matrix.listTenantRooms(req.user.tenant_id),
                server.db
                    .select({
                        userId: authTenantUsers.user_id,
                        email: authUsers.email,
                        firstName: authProfiles.first_name,
                        lastName: authProfiles.last_name,
                        fullName: authProfiles.full_name,
                    })
                    .from(authTenantUsers)
                    .innerJoin(authUsers, eq(authUsers.id, authTenantUsers.user_id))
                    .leftJoin(authProfiles, and(
                        eq(authProfiles.user_id, authTenantUsers.user_id),
                        eq(authProfiles.tenant_id, req.user.tenant_id)
                    ))
                    .where(and(
                        eq(authTenantUsers.tenant_id, req.user.tenant_id),
                        ne(authTenantUsers.user_id, req.user.user_id)
                    ))
            ])
            const roomsByKey = new Map((roomsRes.rooms || []).map((room: any) => [room.key, room]))

            return {
                rooms: userRows.map((user) => {
                    const key = directRoomKey(req.user.user_id, user.userId)
                    const existing = roomsByKey.get(key) as any
                    const name = displayUserName(user)

                    return {
                        ...(existing || {}),
                        key,
                        name,
                        topic: `Direktnachricht mit ${name}`,
                        type: "direct",
                        entityType: "user",
                        entityUuid: user.userId,
                        exists: Boolean(existing?.exists),
                        userId: user.userId,
                        email: user.email,
                    }
                })
            }
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix direct rooms failed")
        }
    })

    server.post("/communication/matrix/direct-rooms/:userId/provision", async (req, reply) => {
        try {
            if (!req.user.tenant_id) return reply.code(400).send({ error: "Kein aktiver Mandant" })
            const params = req.params as { userId: string }
            const [target] = await server.db
                .select({
                    userId: authTenantUsers.user_id,
                    email: authUsers.email,
                    firstName: authProfiles.first_name,
                    lastName: authProfiles.last_name,
                    fullName: authProfiles.full_name,
                })
                .from(authTenantUsers)
                .innerJoin(authUsers, eq(authUsers.id, authTenantUsers.user_id))
                .leftJoin(authProfiles, and(
                    eq(authProfiles.user_id, authTenantUsers.user_id),
                    eq(authProfiles.tenant_id, req.user.tenant_id)
                ))
                .where(and(
                    eq(authTenantUsers.tenant_id, req.user.tenant_id),
                    eq(authTenantUsers.user_id, params.userId)
                ))
                .limit(1)

            if (!target || target.userId === req.user.user_id) {
                return reply.code(404).send({ error: "Benutzer nicht gefunden" })
            }

            const targetName = displayUserName(target)
            return await matrix.provisionTenantRoom(req.user.user_id, req.user.tenant_id, {
                key: directRoomKey(req.user.user_id, target.userId),
                name: targetName,
                topic: `Direktnachricht mit ${targetName}`,
                type: "direct",
                entityType: "user",
                entityUuid: target.userId,
                inviteUserIds: [target.userId],
            })
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix direct room provisioning failed")
        }
    })

    server.post("/communication/matrix/rooms", async (req, reply) => {
        try {
            return await matrix.provisionTenantRoom(
                req.user.user_id,
                req.user.tenant_id,
                roomOptionsFromRequest(req)
            )
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix room provisioning failed")
        }
    })

    server.get("/communication/matrix/rooms/general", async (req, reply) => {
        try {
            return await matrix.getTenantRoomStatus(req.user.tenant_id, "allgemein", "Allgemeiner Chat")
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix room status failed")
        }
    })

    server.post("/communication/matrix/rooms/general/provision", async (req, reply) => {
        try {
            return await matrix.provisionTenantRoom(req.user.user_id, req.user.tenant_id, {
                key: "allgemein",
                name: "Allgemeiner Chat",
            })
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix room provisioning failed")
        }
    })

    server.get("/communication/matrix/rooms/general/messages", async (req, reply) => {
        try {
            return await matrix.getGeneralRoomMessages(req.user.user_id, req.user.tenant_id)
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix messages failed")
        }
    })

    server.get("/communication/matrix/rooms/general/members", async (req, reply) => {
        try {
            return await matrix.getGeneralRoomMembers(req.user.user_id, req.user.tenant_id)
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix members failed")
        }
    })

    server.get("/communication/matrix/users", async (req, reply) => {
        try {
            const users = await matrix.listTenantCommunicationUsers(req.user.tenant_id)
            return { users }
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix users failed")
        }
    })

    server.post("/communication/matrix/rooms/general/session", async (req, reply) => {
        try {
            return await matrix.createElementRoomSession(req.user.user_id, req.user.tenant_id, {
                key: "allgemein",
                name: "Allgemeiner Chat",
            })
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix session failed")
        }
    })

    server.post("/communication/matrix/rooms/general/call-session", async (req, reply) => {
        try {
            const room = {
                key: "allgemein",
                name: "Allgemeiner Chat",
            }
            const session = await matrix.createLiveKitRoomSession(req.user.user_id, req.user.tenant_id, room)
            await notifyTenantUsersAboutCall(req, room, callModeFromRequest(req))
            return session
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix call session failed")
        }
    })

    server.post("/communication/matrix/rooms/general/members/sync", async (req, reply) => {
        try {
            return await matrix.syncTenantRoomMembers(req.user.user_id, req.user.tenant_id, {
                key: "allgemein",
                name: "Allgemeiner Chat",
            })
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix member sync failed")
        }
    })

    server.post("/communication/matrix/rooms/general/messages", async (req, reply) => {
        try {
            const body = req.body as { text?: string; replyToEventId?: string }
            const message = await matrix.sendGeneralRoomMessage(req.user.user_id, req.user.tenant_id, body.text || "", {
                replyToEventId: body.replyToEventId,
            })
            const room = await matrix.getTenantRoomStatus(req.user.tenant_id, "allgemein", "Allgemeiner Chat")
            await notifyUsersAboutChatMessage(req, room, message, body.text || "")
            return message
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix message send failed")
        }
    })

    server.post("/communication/matrix/rooms/general/attachments", async (req, reply) => {
        try {
            const attachment = await uploadedAttachmentFromRequest(req)
            const message = await matrix.sendGeneralRoomAttachment(req.user.user_id, req.user.tenant_id, attachment)
            const room = await matrix.getTenantRoomStatus(req.user.tenant_id, "allgemein", "Allgemeiner Chat")
            await notifyUsersAboutChatMessage(req, room, message, `Anhang: ${attachment.filename}`)
            return message
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix attachment send failed")
        }
    })

    server.get("/communication/matrix/rooms/:roomKey", async (req, reply) => {
        try {
            const params = req.params as { roomKey: string }
            return await matrix.getTenantRoomStatus(req.user.tenant_id, params.roomKey)
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix room status failed")
        }
    })

    server.post("/communication/matrix/rooms/:roomKey/provision", async (req, reply) => {
        try {
            return await matrix.provisionTenantRoom(
                req.user.user_id,
                req.user.tenant_id,
                roomOptionsFromRequest(req)
            )
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix room provisioning failed")
        }
    })

    server.post("/communication/matrix/rooms/:roomKey/read", async (req, reply) => {
        try {
            if (!req.user.tenant_id) return reply.code(400).send({ error: "Kein aktiver Mandant" })
            const params = req.params as { roomKey: string }
            const body = (req.body || {}) as { eventId?: string }
            const result = await markRoomNotificationsRead(req.user.tenant_id, req.user.user_id, params.roomKey)
            if (body.eventId) {
                await matrix.markTenantRoomRead(req.user.user_id, req.user.tenant_id, roomOptionsFromRequest(req), body.eventId)
            }
            return result
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix room read state failed")
        }
    })

    server.get("/communication/matrix/rooms/:roomKey/messages", async (req, reply) => {
        try {
            return await matrix.getTenantRoomMessages(
                req.user.user_id,
                req.user.tenant_id,
                roomOptionsFromRequest(req)
            )
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix messages failed")
        }
    })

    server.get("/communication/matrix/rooms/:roomKey/search", async (req, reply) => {
        try {
            const query = req.query as { q?: string }
            return await matrix.searchTenantRoomMessages(
                req.user.user_id,
                req.user.tenant_id,
                roomOptionsFromRequest(req),
                query.q || ""
            )
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix search failed")
        }
    })

    server.get("/communication/matrix/rooms/:roomKey/sync", async (req, reply) => {
        try {
            const query = req.query as { since?: string; initial?: string }
            const result = await matrix.syncTenantRoomEvents(
                req.user.user_id,
                req.user.tenant_id,
                roomOptionsFromRequest(req),
                query.since,
                query.initial === "1"
            )
            if (query.since && query.initial !== "1" && result.messages?.length) {
                const room = await matrix.getTenantRoomStatus(req.user.tenant_id, result.key, result.name)
                await notifyCurrentUserAboutIncomingMatrixMessages(req, room, result.messages)
            }

            return result
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix sync failed")
        }
    })

    server.get("/communication/matrix/rooms/:roomKey/members", async (req, reply) => {
        try {
            return await matrix.getTenantRoomMembers(
                req.user.user_id,
                req.user.tenant_id,
                roomOptionsFromRequest(req)
            )
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix members failed")
        }
    })

    server.post("/communication/matrix/rooms/:roomKey/members/invite", async (req, reply) => {
        try {
            const body = req.body as { userId?: string }
            return await matrix.inviteTenantRoomMember(
                req.user.user_id,
                req.user.tenant_id,
                roomOptionsFromRequest(req),
                body.userId || ""
            )
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix member invite failed")
        }
    })

    server.delete("/communication/matrix/rooms/:roomKey/members/:matrixUserId", async (req, reply) => {
        try {
            const params = req.params as { matrixUserId: string }
            return await matrix.removeTenantRoomMember(
                req.user.user_id,
                req.user.tenant_id,
                roomOptionsFromRequest(req),
                params.matrixUserId
            )
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix member remove failed")
        }
    })

    server.post("/communication/matrix/rooms/:roomKey/session", async (req, reply) => {
        try {
            return await matrix.createElementRoomSession(
                req.user.user_id,
                req.user.tenant_id,
                roomOptionsFromRequest(req)
            )
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix session failed")
        }
    })

    server.post("/communication/matrix/rooms/:roomKey/call-session", async (req, reply) => {
        try {
            const room = roomOptionsFromRequest(req)
            const session = await matrix.createLiveKitRoomSession(
                req.user.user_id,
                req.user.tenant_id,
                room
            )
            await notifyTenantUsersAboutCall(req, room, callModeFromRequest(req))
            return session
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix call session failed")
        }
    })

    server.post("/communication/matrix/rooms/:roomKey/members/sync", async (req, reply) => {
        try {
            return await matrix.syncTenantRoomMembers(
                req.user.user_id,
                req.user.tenant_id,
                roomOptionsFromRequest(req)
            )
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix member sync failed")
        }
    })

    server.post("/communication/matrix/rooms/:roomKey/messages", async (req, reply) => {
        try {
            const body = req.body as { text?: string; replyToEventId?: string }
            const message = await matrix.sendTenantRoomMessage(
                req.user.user_id,
                req.user.tenant_id,
                roomOptionsFromRequest(req),
                body.text || "",
                {
                    replyToEventId: body.replyToEventId,
                }
            )
            const room = await matrix.getTenantRoomStatus(req.user.tenant_id, message.key)
            await notifyUsersAboutChatMessage(req, room, message, body.text || "")
            return message
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix message send failed")
        }
    })

    server.post("/communication/matrix/rooms/:roomKey/messages/:eventId/reactions", async (req, reply) => {
        try {
            const params = req.params as { eventId: string }
            const body = req.body as { key?: string }
            return await matrix.sendTenantRoomReaction(
                req.user.user_id,
                req.user.tenant_id,
                roomOptionsFromRequest(req),
                params.eventId,
                body.key || ""
            )
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix reaction send failed")
        }
    })

    server.put("/communication/matrix/rooms/:roomKey/messages/:eventId", async (req, reply) => {
        try {
            const params = req.params as { eventId: string }
            const body = req.body as { text?: string }
            return await matrix.editTenantRoomMessage(
                req.user.user_id,
                req.user.tenant_id,
                roomOptionsFromRequest(req),
                params.eventId,
                body.text || ""
            )
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix message edit failed")
        }
    })

    server.delete("/communication/matrix/rooms/:roomKey/messages/:eventId", async (req, reply) => {
        try {
            const params = req.params as { eventId: string }
            return await matrix.redactTenantRoomMessage(
                req.user.user_id,
                req.user.tenant_id,
                roomOptionsFromRequest(req),
                params.eventId
            )
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix message delete failed")
        }
    })

    server.post("/communication/matrix/rooms/:roomKey/attachments", async (req, reply) => {
        try {
            const attachment = await uploadedAttachmentFromRequest(req)
            const message = await matrix.sendTenantRoomAttachment(
                req.user.user_id,
                req.user.tenant_id,
                roomOptionsFromRequest(req),
                attachment
            )
            const room = await matrix.getTenantRoomStatus(req.user.tenant_id, message.key)
            await notifyUsersAboutChatMessage(req, room, message, `Anhang: ${attachment.filename}`)
            return message
        } catch (err: any) {
            return handleMatrixError(req, reply, err, "Matrix attachment send failed")
        }
    })
}
