import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit'
import { db } from '@sim/db'
import { workflow, workflowMcpServer, workflowMcpTool } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { toError } from '@sim/utils/errors'
import { generateId } from '@sim/utils/id'
import { and, eq, inArray, isNull, sql } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { createWorkflowMcpServerBodySchema } from '@/lib/api/contracts/workflow-mcp-servers'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
import { mcpPubSub } from '@/lib/mcp/pubsub'
import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils'
import { generateParameterSchemaForWorkflow } from '@/lib/mcp/workflow-mcp-sync'
import { sanitizeToolName } from '@/lib/mcp/workflow-tool-schema'
import { hasValidStartBlock } from '@/lib/workflows/triggers/trigger-utils.server'

const logger = createLogger('WorkflowMcpServersAPI')

export const dynamic = 'force-dynamic'

/**
 * GET - List all workflow MCP servers for the workspace
 */
export const GET = withRouteHandler(
  withMcpAuth('read')(async (request: NextRequest, { userId, workspaceId, requestId }) => {
    try {
      logger.info(`[${requestId}] Listing workflow MCP servers for workspace ${workspaceId}`)

      const servers = await db
        .select({
          id: workflowMcpServer.id,
          workspaceId: workflowMcpServer.workspaceId,
          createdBy: workflowMcpServer.createdBy,
          name: workflowMcpServer.name,
          description: workflowMcpServer.description,
          isPublic: workflowMcpServer.isPublic,
          createdAt: workflowMcpServer.createdAt,
          updatedAt: workflowMcpServer.updatedAt,
          toolCount: sql<number>`(
            SELECT COUNT(*)::int
            FROM "workflow_mcp_tool"
            WHERE "workflow_mcp_tool"."server_id" = "workflow_mcp_server"."id"
              AND "workflow_mcp_tool"."archived_at" IS NULL
          )`.as('tool_count'),
        })
        .from(workflowMcpServer)
        .where(
          and(eq(workflowMcpServer.workspaceId, workspaceId), isNull(workflowMcpServer.deletedAt))
        )

      const serverIds = servers.map((s) => s.id)
      const tools =
        serverIds.length > 0
          ? await db
              .select({
                serverId: workflowMcpTool.serverId,
                toolName: workflowMcpTool.toolName,
              })
              .from(workflowMcpTool)
              .where(
                and(
                  inArray(workflowMcpTool.serverId, serverIds),
                  isNull(workflowMcpTool.archivedAt)
                )
              )
          : []

      const toolNamesByServer: Record<string, string[]> = {}
      for (const tool of tools) {
        if (!toolNamesByServer[tool.serverId]) {
          toolNamesByServer[tool.serverId] = []
        }
        toolNamesByServer[tool.serverId].push(tool.toolName)
      }

      const serversWithToolNames = servers.map((server) => ({
        ...server,
        toolNames: toolNamesByServer[server.id] || [],
      }))

      logger.info(
        `[${requestId}] Listed ${servers.length} workflow MCP servers for workspace ${workspaceId}`
      )
      return createMcpSuccessResponse({ servers: serversWithToolNames })
    } catch (error) {
      logger.error(`[${requestId}] Error listing workflow MCP servers:`, error)
      return createMcpErrorResponse(toError(error), 'Failed to list workflow MCP servers', 500)
    }
  })
)

/**
 * POST - Create a new workflow MCP server
 */
export const POST = withRouteHandler(
  withMcpAuth('write')(
    async (request: NextRequest, { userId, userName, userEmail, workspaceId, requestId }) => {
      try {
        const rawBody = getParsedBody(request) ?? (await request.json())
        const parsedBody = createWorkflowMcpServerBodySchema.safeParse(rawBody)

        if (!parsedBody.success) {
          return createMcpErrorResponse(parsedBody.error, 'Invalid request format', 400)
        }

        const body = parsedBody.data

        logger.info(`[${requestId}] Creating workflow MCP server:`, {
          name: body.name,
          workspaceId,
          workflowIds: body.workflowIds,
        })

        const serverId = generateId()

        const [server] = await db
          .insert(workflowMcpServer)
          .values({
            id: serverId,
            workspaceId,
            createdBy: userId,
            name: body.name.trim(),
            description: body.description?.trim() || null,
            isPublic: body.isPublic ?? false,
            createdAt: new Date(),
            updatedAt: new Date(),
          })
          .returning()

        const workflowIds: string[] = body.workflowIds || []
        const addedTools: Array<{ workflowId: string; toolName: string }> = []

        if (workflowIds.length > 0) {
          const workflows = await db
            .select({
              id: workflow.id,
              name: workflow.name,
              description: workflow.description,
              isDeployed: workflow.isDeployed,
              workspaceId: workflow.workspaceId,
            })
            .from(workflow)
            .where(and(inArray(workflow.id, workflowIds), isNull(workflow.archivedAt)))

          for (const workflowRecord of workflows) {
            if (workflowRecord.workspaceId !== workspaceId) {
              logger.warn(
                `[${requestId}] Skipping workflow ${workflowRecord.id} - does not belong to workspace`
              )
              continue
            }

            if (!workflowRecord.isDeployed) {
              logger.warn(`[${requestId}] Skipping workflow ${workflowRecord.id} - not deployed`)
              continue
            }

            const hasStartBlock = await hasValidStartBlock(workflowRecord.id)
            if (!hasStartBlock) {
              logger.warn(`[${requestId}] Skipping workflow ${workflowRecord.id} - no start block`)
              continue
            }

            const toolName = sanitizeToolName(workflowRecord.name)
            const toolDescription =
              workflowRecord.description || `Execute ${workflowRecord.name} workflow`

            const parameterSchema = await generateParameterSchemaForWorkflow(workflowRecord.id)

            const toolId = generateId()
            await db.insert(workflowMcpTool).values({
              id: toolId,
              serverId,
              workflowId: workflowRecord.id,
              toolName,
              toolDescription,
              parameterSchema,
              createdAt: new Date(),
              updatedAt: new Date(),
            })

            addedTools.push({ workflowId: workflowRecord.id, toolName })
          }

          logger.info(
            `[${requestId}] Added ${addedTools.length} tools to server ${serverId}:`,
            addedTools.map((t) => t.toolName)
          )

          if (addedTools.length > 0) {
            mcpPubSub?.publishWorkflowToolsChanged({ serverId, workspaceId })
          }
        }

        logger.info(
          `[${requestId}] Successfully created workflow MCP server: ${body.name} (ID: ${serverId})`
        )

        recordAudit({
          workspaceId,
          actorId: userId,
          actorName: userName,
          actorEmail: userEmail,
          action: AuditAction.MCP_SERVER_ADDED,
          resourceType: AuditResourceType.MCP_SERVER,
          resourceId: serverId,
          resourceName: body.name.trim(),
          description: `Published workflow MCP server "${body.name.trim()}" with ${addedTools.length} tool(s)`,
          metadata: {
            serverName: body.name.trim(),
            isPublic: body.isPublic ?? false,
            toolCount: addedTools.length,
            toolNames: addedTools.map((t) => t.toolName),
            workflowIds: addedTools.map((t) => t.workflowId),
          },
          request,
        })

        return createMcpSuccessResponse({ server, addedTools }, 201)
      } catch (error) {
        logger.error(`[${requestId}] Error creating workflow MCP server:`, error)
        return createMcpErrorResponse(toError(error), 'Failed to create workflow MCP server', 500)
      }
    }
  )
)
