import { db } from '@sim/db'
import { chat, workflow } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { generateId } from '@sim/utils/id'
import { and, eq, isNull } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { deployedChatPostContract } from '@/lib/api/contracts/chats'
import { parseRequest } from '@/lib/api/server'
import { releaseExecutionSlot } from '@/lib/billing/calculations/usage-reservation'
import { admissionRejectedResponse, tryAdmit } from '@/lib/core/admission/gate'
import { env } from '@/lib/core/config/env'
import { validateAuthToken } from '@/lib/core/security/deployment'
import { generateRequestId } from '@/lib/core/utils/request'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { preprocessExecution } from '@/lib/execution/preprocessing'
import { LoggingSession } from '@/lib/logs/execution/logging-session'
import { ChatFiles } from '@/lib/uploads'
import { assertChatEmbedAllowed, setChatAuthCookie, validateChatAuth } from '@/app/api/chat/utils'
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'

const logger = createLogger('ChatIdentifierAPI')

interface ChatConfigSource {
  id: string
  title: string
  description: string | null
  customizations: unknown
  authType: string | null
  outputConfigs: unknown
}

function toChatConfigResponse(deployment: ChatConfigSource) {
  return {
    id: deployment.id,
    title: deployment.title,
    description: deployment.description,
    customizations: deployment.customizations,
    authType: deployment.authType,
    outputConfigs: deployment.outputConfigs,
  }
}

export const dynamic = 'force-dynamic'
export const runtime = 'nodejs'

const CHAT_MAX_REQUEST_BYTES = Number.parseInt(env.CHAT_MAX_REQUEST_BYTES, 10) || 220 * 1024 * 1024

export const POST = withRouteHandler(
  async (request: NextRequest, context: { params: Promise<{ identifier: string }> }) => {
    const { identifier } = await context.params
    const requestId = generateRequestId()

    const ticket = tryAdmit()
    if (!ticket) {
      return admissionRejectedResponse()
    }

    try {
      const parsed = await parseRequest(deployedChatPostContract, request, context, {
        maxBodyBytes: CHAT_MAX_REQUEST_BYTES,
        validationErrorResponse: (err) => {
          const message = err.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')
          return createErrorResponse(`Invalid request body: ${message}`, 400, 'VALIDATION_ERROR')
        },
        invalidJsonResponse: () => createErrorResponse('Invalid request body', 400),
      })
      if (!parsed.success) return parsed.response
      const parsedBody = parsed.data.body

      const deploymentResult = await db
        .select({
          id: chat.id,
          title: chat.title,
          description: chat.description,
          customizations: chat.customizations,
          workflowId: chat.workflowId,
          userId: chat.userId,
          isActive: chat.isActive,
          authType: chat.authType,
          password: chat.password,
          allowedEmails: chat.allowedEmails,
          outputConfigs: chat.outputConfigs,
        })
        .from(chat)
        .where(and(eq(chat.identifier, identifier), isNull(chat.archivedAt)))
        .limit(1)

      if (deploymentResult.length === 0) {
        logger.warn(`[${requestId}] Chat not found for identifier: ${identifier}`)
        return createErrorResponse('Chat not found', 404)
      }

      const deployment = deploymentResult[0]

      if (!deployment.isActive) {
        logger.warn(`[${requestId}] Chat is not active: ${identifier}`)

        const [workflowRecord] = await db
          .select({ workspaceId: workflow.workspaceId })
          .from(workflow)
          .where(and(eq(workflow.id, deployment.workflowId), isNull(workflow.archivedAt)))
          .limit(1)

        const workspaceId = workflowRecord?.workspaceId
        if (!workspaceId) {
          logger.warn(
            `[${requestId}] Cannot log: workflow ${deployment.workflowId} has no workspace`
          )
          return createErrorResponse('This chat is currently unavailable', 403)
        }

        const executionId = generateId()
        const loggingSession = new LoggingSession(
          deployment.workflowId,
          executionId,
          'chat',
          requestId
        )

        await loggingSession.safeStart({
          userId: deployment.userId,
          workspaceId,
          variables: {},
        })

        await loggingSession.safeCompleteWithError({
          error: {
            message: 'This chat is currently unavailable. The chat has been disabled.',
            stackTrace: undefined,
          },
          traceSpans: [],
        })

        return createErrorResponse('This chat is currently unavailable', 403)
      }

      const embedBlock = await assertChatEmbedAllowed(request, deployment.workflowId, requestId)
      if (embedBlock) return embedBlock

      const authResult = await validateChatAuth(requestId, deployment, request, parsedBody)
      if (!authResult.authorized) {
        const response = createErrorResponse(
          authResult.error || 'Authentication required',
          authResult.status || 401
        )
        if (authResult.status === 429 && authResult.retryAfterMs !== undefined) {
          response.headers.set('Retry-After', String(Math.ceil(authResult.retryAfterMs / 1000)))
        }
        return response
      }

      const { input, password, email, conversationId, files } = parsedBody

      if ((password || email) && !input) {
        const response = createSuccessResponse(toChatConfigResponse(deployment))

        if (deployment.authType !== 'sso') {
          setChatAuthCookie(response, deployment.id, deployment.authType, deployment.password)
        }

        return response
      }

      if (!input && (!files || files.length === 0)) {
        return createErrorResponse('No input provided', 400)
      }

      const executionId = generateId()

      const loggingSession = new LoggingSession(
        deployment.workflowId,
        executionId,
        'chat',
        requestId
      )

      const preprocessResult = await preprocessExecution({
        workflowId: deployment.workflowId,
        userId: deployment.userId,
        triggerType: 'chat',
        executionId,
        requestId,
        checkRateLimit: true,
        checkDeployment: true,
        loggingSession,
      })

      if (!preprocessResult.success) {
        logger.warn(`[${requestId}] Preprocessing failed: ${preprocessResult.error?.message}`)
        return createErrorResponse(
          preprocessResult.error?.message || 'Failed to process request',
          preprocessResult.error?.statusCode || 500
        )
      }

      const { actorUserId, workflowRecord } = preprocessResult
      const workspaceOwnerId = actorUserId!
      const workspaceId = workflowRecord?.workspaceId
      if (!workspaceId) {
        logger.error(`[${requestId}] Workflow ${deployment.workflowId} has no workspaceId`)
        // preprocessExecution reserved a billing concurrency slot; release it on
        // this early exit since no LoggingSession will finalize to free it.
        await releaseExecutionSlot(executionId)
        return createErrorResponse('Workflow has no associated workspace', 500)
      }

      try {
        const selectedOutputs: string[] = []
        if (deployment.outputConfigs && Array.isArray(deployment.outputConfigs)) {
          for (const config of deployment.outputConfigs) {
            const outputId = config.path
              ? `${config.blockId}_${config.path}`
              : `${config.blockId}_content`
            selectedOutputs.push(outputId)
          }
        }

        const { createStreamingResponse } = await import('@/lib/workflows/streaming/streaming')
        const { executeWorkflow } = await import('@/lib/workflows/executor/execute-workflow')
        const { SSE_HEADERS } = await import('@/lib/core/utils/sse')

        const workflowInput: any = { input, conversationId }
        if (files && Array.isArray(files) && files.length > 0) {
          const executionContext = {
            workspaceId,
            workflowId: deployment.workflowId,
            executionId,
          }

          try {
            const uploadedFiles = await ChatFiles.processChatFiles(
              files,
              executionContext,
              requestId,
              deployment.userId
            )

            if (uploadedFiles.length > 0) {
              workflowInput.files = uploadedFiles
              logger.info(`[${requestId}] Successfully processed ${uploadedFiles.length} files`)
            }
          } catch (fileError: any) {
            logger.error(`[${requestId}] Failed to process chat files:`, fileError)

            await loggingSession.safeStart({
              userId: workspaceOwnerId,
              workspaceId,
              variables: {},
            })

            await loggingSession.safeCompleteWithError({
              error: {
                message: `File upload failed: ${fileError.message || 'Unable to process uploaded files'}`,
                stackTrace: fileError.stack,
              },
              traceSpans: [],
            })

            throw fileError
          }
        }

        const workflowForExecution = {
          id: deployment.workflowId,
          userId: deployment.userId,
          workspaceId,
          isDeployed: workflowRecord?.isDeployed ?? false,
          variables: (workflowRecord?.variables as Record<string, unknown>) ?? undefined,
        }

        const stream = await createStreamingResponse({
          requestId,
          streamConfig: {
            selectedOutputs,
            isSecureMode: true,
            workflowTriggerType: 'chat',
          },
          executionId,
          workspaceId,
          workflowId: deployment.workflowId,
          userId: workspaceOwnerId,
          executeFn: async ({ onStream, onBlockComplete, abortSignal }) =>
            executeWorkflow(
              workflowForExecution,
              requestId,
              workflowInput,
              workspaceOwnerId,
              {
                enabled: true,
                selectedOutputs,
                isSecureMode: true,
                workflowTriggerType: 'chat',
                onStream,
                onBlockComplete,
                skipLoggingComplete: true,
                abortSignal,
                executionMode: 'stream',
              },
              executionId
            ),
        })

        const streamResponse = new NextResponse(stream, {
          status: 200,
          headers: SSE_HEADERS,
        })
        return streamResponse
      } catch (error: any) {
        logger.error(`[${requestId}] Error processing chat request:`, error)
        // Setup failed before the workflow stream took over slot release;
        // free the reserved billing slot (idempotent if already released).
        await releaseExecutionSlot(executionId)
        return createErrorResponse(error.message || 'Failed to process request', 500)
      }
    } catch (error: any) {
      logger.error(`[${requestId}] Error processing chat request:`, error)
      return createErrorResponse(error.message || 'Failed to process request', 500)
    } finally {
      ticket.release()
    }
  }
)

export const GET = withRouteHandler(
  async (request: NextRequest, { params }: { params: Promise<{ identifier: string }> }) => {
    const { identifier } = await params
    const requestId = generateRequestId()

    try {
      const deploymentResult = await db
        .select({
          id: chat.id,
          title: chat.title,
          description: chat.description,
          customizations: chat.customizations,
          isActive: chat.isActive,
          workflowId: chat.workflowId,
          authType: chat.authType,
          password: chat.password,
          allowedEmails: chat.allowedEmails,
          outputConfigs: chat.outputConfigs,
        })
        .from(chat)
        .where(and(eq(chat.identifier, identifier), isNull(chat.archivedAt)))
        .limit(1)

      if (deploymentResult.length === 0) {
        logger.warn(`[${requestId}] Chat not found for identifier: ${identifier}`)
        return createErrorResponse('Chat not found', 404)
      }

      const deployment = deploymentResult[0]

      if (!deployment.isActive) {
        logger.warn(`[${requestId}] Chat is not active: ${identifier}`)
        return createErrorResponse('This chat is currently unavailable', 403)
      }

      const embedBlock = await assertChatEmbedAllowed(request, deployment.workflowId, requestId)
      if (embedBlock) return embedBlock

      const cookieName = `chat_auth_${deployment.id}`
      const authCookie = request.cookies.get(cookieName)

      if (
        deployment.authType !== 'public' &&
        deployment.authType !== 'sso' &&
        authCookie &&
        validateAuthToken(authCookie.value, deployment.id, deployment.authType, deployment.password)
      ) {
        return createSuccessResponse(toChatConfigResponse(deployment))
      }

      const authResult = await validateChatAuth(requestId, deployment, request)
      if (!authResult.authorized) {
        logger.info(
          `[${requestId}] Authentication required for chat: ${identifier}, type: ${deployment.authType}`
        )
        return createErrorResponse(authResult.error || 'Authentication required', 401)
      }

      return createSuccessResponse(toChatConfigResponse(deployment))
    } catch (error: any) {
      logger.error(`[${requestId}] Error fetching chat info:`, error)
      return createErrorResponse(error.message || 'Failed to fetch chat information', 500)
    }
  }
)
