import { FastifyInstance } from "fastify"
import { createHash, randomBytes } from "node:crypto"
import { and, desc, eq } from "drizzle-orm"
import { z } from "zod"
import { instanceAgentScanJobs, instanceAgents } from "../../db/schema"

const createAgentSchema = z.object({
    name: z.string().min(1),
    description: z.string().optional().nullable(),
})

const updateAgentSchema = z.object({
    name: z.string().min(1).optional(),
    description: z.string().optional().nullable(),
    active: z.boolean().optional(),
    preferredScannerName: z.string().optional().nullable(),
    scanDefaults: z.record(z.string(), z.any()).optional(),
})

const createScanJobSchema = z.object({
    agentId: z.string().uuid(),
    tenantId: z.number().int().positive().optional(),
    scannerName: z.string().optional().nullable(),
    requestedFilename: z.string().optional().nullable(),
    settings: z.record(z.string(), z.any()).optional(),
    target: z.record(z.string(), z.any()).optional(),
})

const hashToken = (token: string) =>
    createHash("sha256").update(token, "utf8").digest("hex")

const createAgentToken = () => `fedeo_agent_${randomBytes(32).toString("hex")}`

const requireAdmin = (req: any, reply: any) => {
    if (!req.user?.is_admin) {
        reply.code(403).send({ error: "Admin required" })
        return false
    }

    return true
}

export default async function instanceAgentRoutes(server: FastifyInstance) {
    server.get("/instance-agents", async () => {
        const rows = await server.db
            .select({
                id: instanceAgents.id,
                createdAt: instanceAgents.createdAt,
                updatedAt: instanceAgents.updatedAt,
                name: instanceAgents.name,
                description: instanceAgents.description,
                tokenPrefix: instanceAgents.tokenPrefix,
                active: instanceAgents.active,
                capabilities: instanceAgents.capabilities,
                scannerNames: instanceAgents.scannerNames,
                printerNames: instanceAgents.printerNames,
                preferredScannerName: instanceAgents.preferredScannerName,
                scanDefaults: instanceAgents.scanDefaults,
                lastSeenAt: instanceAgents.lastSeenAt,
                lastDebugInfo: instanceAgents.lastDebugInfo,
            })
            .from(instanceAgents)
            .orderBy(desc(instanceAgents.createdAt))

        return { agents: rows }
    })

    server.post("/instance-agents", async (req, reply) => {
        if (!requireAdmin(req, reply)) return

        const body = createAgentSchema.parse(req.body)
        const token = createAgentToken()

        const [agent] = await server.db
            .insert(instanceAgents)
            .values({
                name: body.name,
                description: body.description,
                tokenPrefix: token.slice(0, 24),
                tokenHash: hashToken(token),
            })
            .returning({
                id: instanceAgents.id,
                name: instanceAgents.name,
                description: instanceAgents.description,
                tokenPrefix: instanceAgents.tokenPrefix,
                active: instanceAgents.active,
                preferredScannerName: instanceAgents.preferredScannerName,
                scanDefaults: instanceAgents.scanDefaults,
                createdAt: instanceAgents.createdAt,
            })

        return {
            agent,
            token,
        }
    })

    server.patch<{ Params: { id: string } }>("/instance-agents/:id", async (req, reply) => {
        if (!requireAdmin(req, reply)) return

        const body = updateAgentSchema.parse(req.body)
        const [agent] = await server.db
            .update(instanceAgents)
            .set({
                ...body,
                updatedAt: new Date(),
            })
            .where(eq(instanceAgents.id, req.params.id))
            .returning({
                id: instanceAgents.id,
                name: instanceAgents.name,
                description: instanceAgents.description,
                active: instanceAgents.active,
                preferredScannerName: instanceAgents.preferredScannerName,
                scanDefaults: instanceAgents.scanDefaults,
                updatedAt: instanceAgents.updatedAt,
            })

        if (!agent) return reply.code(404).send({ error: "Agent not found" })

        return { agent }
    })

    server.post("/scan-jobs", async (req, reply) => {
        const body = createScanJobSchema.parse(req.body)
        const requestedTenantId = body.tenantId || req.user?.tenant_id

        if (!requestedTenantId) {
            return reply.code(400).send({ error: "tenantId required" })
        }

        if (body.tenantId && body.tenantId !== req.user?.tenant_id && !req.user?.is_admin) {
            return reply.code(403).send({ error: "Cannot create scan job for another tenant" })
        }

        const [agent] = await server.db
            .select({
                id: instanceAgents.id,
                active: instanceAgents.active,
                preferredScannerName: instanceAgents.preferredScannerName,
                scanDefaults: instanceAgents.scanDefaults,
            })
            .from(instanceAgents)
            .where(eq(instanceAgents.id, body.agentId))
            .limit(1)

        if (!agent || !agent.active) {
            return reply.code(404).send({ error: "Active agent not found" })
        }

        const [job] = await server.db
            .insert(instanceAgentScanJobs)
            .values({
                tenantId: requestedTenantId,
                agentId: body.agentId,
                requestedBy: req.user?.user_id,
                scannerName: body.scannerName || agent.preferredScannerName,
                requestedFilename: body.requestedFilename,
                settings: {
                    ...((agent.scanDefaults || {}) as Record<string, any>),
                    ...(body.settings || {}),
                },
                target: body.target || {},
            })
            .returning()

        return { job }
    })

    server.get("/scan-jobs", async (req) => {
        const query = req.query as { tenantId?: string }
        const tenantId = req.user?.is_admin && query.tenantId
            ? Number(query.tenantId)
            : req.user?.tenant_id

        const rows = tenantId
            ? await server.db
                .select()
                .from(instanceAgentScanJobs)
                .where(eq(instanceAgentScanJobs.tenantId, tenantId))
                .orderBy(desc(instanceAgentScanJobs.createdAt))
            : await server.db
                .select()
                .from(instanceAgentScanJobs)
                .orderBy(desc(instanceAgentScanJobs.createdAt))

        return { jobs: rows }
    })

    server.get<{ Params: { id: string } }>("/scan-jobs/:id", async (req, reply) => {
        const conditions = [eq(instanceAgentScanJobs.id, req.params.id)]

        if (!req.user?.is_admin) {
            if (!req.user?.tenant_id) return reply.code(400).send({ error: "tenant required" })
            conditions.push(eq(instanceAgentScanJobs.tenantId, req.user.tenant_id))
        }

        const [job] = await server.db
            .select()
            .from(instanceAgentScanJobs)
            .where(and(...conditions))
            .limit(1)

        if (!job) return reply.code(404).send({ error: "Scan job not found" })

        return { job }
    })
}
