import { FastifyInstance } from "fastify"
import fp from "fastify-plugin"
import jwt from "jsonwebtoken"
import { secrets } from "../utils/secrets"
import { createHash } from "node:crypto"

import {
    authUserRoles,
    authRolePermissions,
    authUsers,
    m2mApiKeys,
    tenants,
} from "../../db/schema"

import { eq, and, inArray } from "drizzle-orm"

export default fp(async (server: FastifyInstance) => {
    const hashApiKey = (apiKey: string) =>
        createHash("sha256").update(apiKey, "utf8").digest("hex")

    const isMcpRoute = (url: string) =>
        url === "/mcp" ||
        url.startsWith("/mcp/") ||
        url === "/api/mcp" ||
        url.startsWith("/api/mcp/")

    const isWriteMethod = (method: string) =>
        ["POST", "PUT", "PATCH", "DELETE"].includes(method)

    const isTenantLockAllowedRoute = (urlPath: string) =>
        urlPath === "/api/auth/me" ||
        urlPath === "/auth/me" ||
        urlPath === "/api/tenant/switch" ||
        urlPath === "/tenant/switch" ||
        urlPath.startsWith("/api/admin/") ||
        urlPath.startsWith("/admin/") ||
        urlPath.startsWith("/api/auth/") ||
        urlPath.startsWith("/auth/")

    const authenticateMcpApiKey = async (apiKey: string) => {
        if (!apiKey.startsWith("fedeo_mcp_")) return false

        const keyHash = hashApiKey(apiKey)

        const rows = await server.db
            .select({
                id: m2mApiKeys.id,
                tenantId: m2mApiKeys.tenantId,
                userId: m2mApiKeys.userId,
                active: m2mApiKeys.active,
                expiresAt: m2mApiKeys.expiresAt,
                userEmail: authUsers.email,
                isAdmin: authUsers.is_admin,
            })
            .from(m2mApiKeys)
            .innerJoin(authUsers, eq(authUsers.id, m2mApiKeys.userId))
            .where(and(
                eq(m2mApiKeys.keyHash, keyHash),
                eq(m2mApiKeys.active, true)
            ))
            .limit(1)

        const key = rows[0]
        if (!key) return false
        if (key.expiresAt && new Date(key.expiresAt).getTime() < Date.now()) return false

        await server.db
            .update(m2mApiKeys)
            .set({ lastUsedAt: new Date(), updatedAt: new Date() })
            .where(eq(m2mApiKeys.id, key.id))

        return {
            user_id: key.userId,
            email: key.userEmail,
            tenant_id: key.tenantId,
            is_admin: Boolean(key.isAdmin),
        }
    }

    server.addHook("preHandler", async (req, reply) => {
        if (req.method === "OPTIONS") {
            return
        }

        const urlPath = req.url.split("?")[0]
        const queryToken = (req.query as any)?.downloadToken
        const downloadToken =
            typeof queryToken === "string"
            && urlPath.startsWith("/api/email/attachments/")
            && urlPath.endsWith("/download")
                ? queryToken
                : null

        // 1️⃣ Token aus Header oder Cookie lesen
        const cookieToken = req.cookies?.token
        const authHeader = req.headers.authorization

        const headerToken =
            authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null

        const token =
            headerToken && headerToken.length > 10
                ? headerToken
                : cookieToken || downloadToken || null

        if (!token) {
            return reply.code(401).send({ error: "Authentication required" })
        }

        try {
            // 2️⃣ JWT verifizieren oder für MCP dauerhaften Token akzeptieren
            let payload: {
                user_id: string
                email: string
                tenant_id: number | null
                is_admin?: boolean
            }

            try {
                payload = jwt.verify(token, secrets.JWT_SECRET!) as {
                    user_id: string
                    email: string
                    tenant_id: number | null
                }
            } catch (jwtError) {
                const mcpPayload = isMcpRoute(req.url)
                    ? await authenticateMcpApiKey(token)
                    : false

                if (!mcpPayload) {
                    throw jwtError
                }

                payload = mcpPayload
                ;(req as any).mcpTokenAuth = true
            }

            if (!payload?.user_id) {
                return reply.code(401).send({ error: "Invalid token" })
            }

            // Payload an Request hängen
            req.user = payload

            if (typeof payload.is_admin === "boolean") {
                req.user.is_admin = payload.is_admin
            } else {
                const [currentUser] = await server.db
                    .select({
                        is_admin: authUsers.is_admin,
                    })
                    .from(authUsers)
                    .where(eq(authUsers.id, payload.user_id))
                    .limit(1)

                req.user.is_admin = Boolean(currentUser?.is_admin)
            }

            // Multi-Tenant Modus ohne ausgewählten Tenant → keine Rollenprüfung
            if (!req.user.tenant_id) {
                return
            }

            const tenantId = req.user.tenant_id
            const userId = req.user.user_id

            if (isWriteMethod(req.method) && !isTenantLockAllowedRoute(urlPath)) {
                const [tenant] = await server.db
                    .select({
                        locked: tenants.locked,
                        lockedByExportJobId: tenants.lockedByExportJobId,
                    })
                    .from(tenants)
                    .where(eq(tenants.id, tenantId))
                    .limit(1)

                if (tenant?.locked === "maintenance_tenant") {
                    return reply.code(423).send({
                        error: "Tenant is locked for maintenance",
                        locked: tenant.locked,
                        lockedByExportJobId: tenant.lockedByExportJobId,
                    })
                }
            }

            // --------------------------------------------------------
            // 3️⃣ Rollen des Nutzers im Tenant holen
            // --------------------------------------------------------
            const roleRows = await server.db
                .select({
                    role_id: authUserRoles.role_id,
                })
                .from(authUserRoles)
                .where(
                    and(
                        eq(authUserRoles.user_id, userId),
                        eq(authUserRoles.tenant_id, tenantId)
                    )
                )

            if (roleRows.length === 0) {
                if (req.user.is_admin) {
                    req.role = ""
                    req.permissions = []
                    req.hasPermission = () => false
                    return
                }

                return reply
                    .code(403)
                    .send({ error: "No role assigned for this tenant" })
            }

            const roleIds = Array.from(new Set(roleRows.map((role) => role.role_id)))

            // --------------------------------------------------------
            // 4️⃣ Berechtigungen der Rollen laden
            // --------------------------------------------------------
            const permissionRows = await server.db
                .select()
                .from(authRolePermissions)
                .where(inArray(authRolePermissions.role_id, roleIds))

            const permissions = Array.from(new Set(permissionRows.map((p) => p.permission)))

            // --------------------------------------------------------
            // 5️⃣ An Request hängen für spätere Nutzung
            // --------------------------------------------------------
            req.role = roleIds[0]
            req.permissions = permissions
            req.hasPermission = (perm: string) => permissions.includes(perm)

        } catch (err) {
            console.error("JWT verification error:", err)
            return reply.code(401).send({ error: "Invalid or expired token" })
        }
    })
})

// ---------------------------------------------------------------------------
// Fastify TypeScript Erweiterungen
// ---------------------------------------------------------------------------

declare module "fastify" {
    interface FastifyRequest {
        user: {
            user_id: string
            email: string
            tenant_id: number | null
            is_admin?: boolean
        }
        role: string
        permissions: string[]
        hasPermission: (permission: string) => boolean
    }
}
