import { db } from '@sim/db'
import { webhook, workflow } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { validateInteger } from '@/lib/core/security/input-validation'
import { PlatformEvents } from '@/lib/core/telemetry'
import { generateRequestId } from '@/lib/core/utils/request'
import {
  cleanupExternalWebhook,
  createExternalWebhookSubscription,
  shouldRecreateExternalWebhookSubscription,
} from '@/lib/webhooks/provider-subscriptions'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'

const logger = createLogger('WebhookAPI')

export const dynamic = 'force-dynamic'

// Get a specific webhook
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
  const requestId = generateRequestId()

  try {
    const { id } = await params
    logger.debug(`[${requestId}] Fetching webhook with ID: ${id}`)

    const session = await getSession()
    if (!session?.user?.id) {
      logger.warn(`[${requestId}] Unauthorized webhook access attempt`)
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
    }

    const webhooks = await db
      .select({
        webhook: webhook,
        workflow: {
          id: workflow.id,
          name: workflow.name,
          userId: workflow.userId,
          workspaceId: workflow.workspaceId,
        },
      })
      .from(webhook)
      .innerJoin(workflow, eq(webhook.workflowId, workflow.id))
      .where(eq(webhook.id, id))
      .limit(1)

    if (webhooks.length === 0) {
      logger.warn(`[${requestId}] Webhook not found: ${id}`)
      return NextResponse.json({ error: 'Webhook not found' }, { status: 404 })
    }

    const webhookData = webhooks[0]

    // Check if user has permission to access this webhook
    let hasAccess = false

    // Case 1: User owns the workflow
    if (webhookData.workflow.userId === session.user.id) {
      hasAccess = true
    }

    // Case 2: Workflow belongs to a workspace and user has any permission
    if (!hasAccess && webhookData.workflow.workspaceId) {
      const userPermission = await getUserEntityPermissions(
        session.user.id,
        'workspace',
        webhookData.workflow.workspaceId
      )
      if (userPermission !== null) {
        hasAccess = true
      }
    }

    if (!hasAccess) {
      logger.warn(`[${requestId}] User ${session.user.id} denied access to webhook: ${id}`)
      return NextResponse.json({ error: 'Access denied' }, { status: 403 })
    }

    logger.info(`[${requestId}] Successfully retrieved webhook: ${id}`)
    return NextResponse.json({ webhook: webhooks[0] }, { status: 200 })
  } catch (error) {
    logger.error(`[${requestId}] Error fetching webhook`, error)
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
  }
}

// Update a webhook
export async function PATCH(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
  const requestId = generateRequestId()

  try {
    const { id } = await params
    logger.debug(`[${requestId}] Updating webhook with ID: ${id}`)

    const session = await getSession()
    if (!session?.user?.id) {
      logger.warn(`[${requestId}] Unauthorized webhook update attempt`)
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
    }

    const body = await request.json()
    const { path, provider, providerConfig, isActive, failedCount } = body

    if (failedCount !== undefined) {
      const validation = validateInteger(failedCount, 'failedCount', { min: 0 })
      if (!validation.isValid) {
        logger.warn(`[${requestId}] ${validation.error}`)
        return NextResponse.json({ error: validation.error }, { status: 400 })
      }
    }

    let resolvedProviderConfig = providerConfig
    if (providerConfig) {
      const { resolveEnvVarsInObject } = await import('@/lib/webhooks/env-resolver')
      const webhookDataForResolve = await db
        .select({
          workspaceId: workflow.workspaceId,
        })
        .from(webhook)
        .innerJoin(workflow, eq(webhook.workflowId, workflow.id))
        .where(eq(webhook.id, id))
        .limit(1)

      if (webhookDataForResolve.length > 0) {
        resolvedProviderConfig = await resolveEnvVarsInObject(
          providerConfig,
          session.user.id,
          webhookDataForResolve[0].workspaceId || undefined
        )
      }
    }

    // Find the webhook and check permissions
    const webhooks = await db
      .select({
        webhook: webhook,
        workflow: {
          id: workflow.id,
          userId: workflow.userId,
          workspaceId: workflow.workspaceId,
        },
      })
      .from(webhook)
      .innerJoin(workflow, eq(webhook.workflowId, workflow.id))
      .where(eq(webhook.id, id))
      .limit(1)

    if (webhooks.length === 0) {
      logger.warn(`[${requestId}] Webhook not found: ${id}`)
      return NextResponse.json({ error: 'Webhook not found' }, { status: 404 })
    }

    const webhookData = webhooks[0]

    // Check if user has permission to modify this webhook
    let canModify = false

    // Case 1: User owns the workflow
    if (webhookData.workflow.userId === session.user.id) {
      canModify = true
    }

    // Case 2: Workflow belongs to a workspace and user has write or admin permission
    if (!canModify && webhookData.workflow.workspaceId) {
      const userPermission = await getUserEntityPermissions(
        session.user.id,
        'workspace',
        webhookData.workflow.workspaceId
      )
      if (userPermission === 'write' || userPermission === 'admin') {
        canModify = true
      }
    }

    if (!canModify) {
      logger.warn(
        `[${requestId}] User ${session.user.id} denied permission to modify webhook: ${id}`
      )
      return NextResponse.json({ error: 'Access denied' }, { status: 403 })
    }

    const existingProviderConfig =
      (webhookData.webhook.providerConfig as Record<string, unknown>) || {}
    let nextProviderConfig =
      providerConfig !== undefined &&
      resolvedProviderConfig &&
      typeof resolvedProviderConfig === 'object'
        ? (resolvedProviderConfig as Record<string, unknown>)
        : existingProviderConfig
    const nextProvider = (provider ?? webhookData.webhook.provider) as string

    if (
      providerConfig !== undefined &&
      shouldRecreateExternalWebhookSubscription({
        previousProvider: webhookData.webhook.provider as string,
        nextProvider,
        previousConfig: existingProviderConfig,
        nextConfig: nextProviderConfig,
      })
    ) {
      await cleanupExternalWebhook(
        { ...webhookData.webhook, providerConfig: existingProviderConfig },
        webhookData.workflow,
        requestId
      )

      const result = await createExternalWebhookSubscription(
        request,
        {
          ...webhookData.webhook,
          provider: nextProvider,
          providerConfig: nextProviderConfig,
        },
        webhookData.workflow,
        session.user.id,
        requestId
      )

      nextProviderConfig = result.updatedProviderConfig as Record<string, unknown>
    }

    logger.debug(`[${requestId}] Updating webhook properties`, {
      hasPathUpdate: path !== undefined,
      hasProviderUpdate: provider !== undefined,
      hasConfigUpdate: providerConfig !== undefined,
      hasActiveUpdate: isActive !== undefined,
      hasFailedCountUpdate: failedCount !== undefined,
    })

    // Merge providerConfig to preserve credential-related fields
    let finalProviderConfig = webhooks[0].webhook.providerConfig
    if (providerConfig !== undefined) {
      const existingConfig = existingProviderConfig
      finalProviderConfig = {
        ...nextProviderConfig,
        credentialId: existingConfig.credentialId,
        credentialSetId: existingConfig.credentialSetId,
        userId: existingConfig.userId,
        historyId: existingConfig.historyId,
        lastCheckedTimestamp: existingConfig.lastCheckedTimestamp,
        setupCompleted: existingConfig.setupCompleted,
        externalId: nextProviderConfig.externalId ?? existingConfig.externalId,
      }
    }

    const updatedWebhook = await db
      .update(webhook)
      .set({
        path: path !== undefined ? path : webhooks[0].webhook.path,
        provider: provider !== undefined ? provider : webhooks[0].webhook.provider,
        providerConfig: finalProviderConfig,
        isActive: isActive !== undefined ? isActive : webhooks[0].webhook.isActive,
        failedCount: failedCount !== undefined ? failedCount : webhooks[0].webhook.failedCount,
        updatedAt: new Date(),
      })
      .where(eq(webhook.id, id))
      .returning()

    logger.info(`[${requestId}] Successfully updated webhook: ${id}`)
    return NextResponse.json({ webhook: updatedWebhook[0] }, { status: 200 })
  } catch (error) {
    logger.error(`[${requestId}] Error updating webhook`, error)
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
  }
}

// Delete a webhook
export async function DELETE(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const requestId = generateRequestId()

  try {
    const { id } = await params
    logger.debug(`[${requestId}] Deleting webhook with ID: ${id}`)

    const session = await getSession()
    if (!session?.user?.id) {
      logger.warn(`[${requestId}] Unauthorized webhook deletion attempt`)
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
    }

    // Find the webhook and check permissions
    const webhooks = await db
      .select({
        webhook: webhook,
        workflow: {
          id: workflow.id,
          userId: workflow.userId,
          workspaceId: workflow.workspaceId,
        },
      })
      .from(webhook)
      .innerJoin(workflow, eq(webhook.workflowId, workflow.id))
      .where(eq(webhook.id, id))
      .limit(1)

    if (webhooks.length === 0) {
      logger.warn(`[${requestId}] Webhook not found: ${id}`)
      return NextResponse.json({ error: 'Webhook not found' }, { status: 404 })
    }

    const webhookData = webhooks[0]

    // Check if user has permission to delete this webhook
    let canDelete = false

    // Case 1: User owns the workflow
    if (webhookData.workflow.userId === session.user.id) {
      canDelete = true
    }

    // Case 2: Workflow belongs to a workspace and user has write or admin permission
    if (!canDelete && webhookData.workflow.workspaceId) {
      const userPermission = await getUserEntityPermissions(
        session.user.id,
        'workspace',
        webhookData.workflow.workspaceId
      )
      if (userPermission === 'write' || userPermission === 'admin') {
        canDelete = true
      }
    }

    if (!canDelete) {
      logger.warn(
        `[${requestId}] User ${session.user.id} denied permission to delete webhook: ${id}`
      )
      return NextResponse.json({ error: 'Access denied' }, { status: 403 })
    }

    const foundWebhook = webhookData.webhook
    const { cleanupExternalWebhook } = await import('@/lib/webhooks/provider-subscriptions')

    const providerConfig = foundWebhook.providerConfig as Record<string, unknown> | null
    const credentialSetId = providerConfig?.credentialSetId as string | undefined
    const blockId = providerConfig?.blockId as string | undefined

    if (credentialSetId && blockId) {
      const allCredentialSetWebhooks = await db
        .select()
        .from(webhook)
        .where(and(eq(webhook.workflowId, webhookData.workflow.id), eq(webhook.blockId, blockId)))

      const webhooksToDelete = allCredentialSetWebhooks.filter((w) => {
        const config = w.providerConfig as Record<string, unknown> | null
        return config?.credentialSetId === credentialSetId
      })

      for (const w of webhooksToDelete) {
        await cleanupExternalWebhook(w, webhookData.workflow, requestId)
      }

      const idsToDelete = webhooksToDelete.map((w) => w.id)
      for (const wId of idsToDelete) {
        await db.delete(webhook).where(eq(webhook.id, wId))
      }

      try {
        for (const wId of idsToDelete) {
          PlatformEvents.webhookDeleted({
            webhookId: wId,
            workflowId: webhookData.workflow.id,
          })
        }
      } catch {
        // Telemetry should not fail the operation
      }

      logger.info(
        `[${requestId}] Successfully deleted ${idsToDelete.length} webhooks for credential set`,
        {
          credentialSetId,
          blockId,
          deletedIds: idsToDelete,
        }
      )
    } else {
      await cleanupExternalWebhook(foundWebhook, webhookData.workflow, requestId)
      await db.delete(webhook).where(eq(webhook.id, id))

      try {
        PlatformEvents.webhookDeleted({
          webhookId: id,
          workflowId: webhookData.workflow.id,
        })
      } catch {
        // Telemetry should not fail the operation
      }

      logger.info(`[${requestId}] Successfully deleted webhook: ${id}`)
    }

    return NextResponse.json({ success: true }, { status: 200 })
  } catch (error: any) {
    logger.error(`[${requestId}] Error deleting webhook`, {
      error: error.message,
      stack: error.stack,
    })
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
  }
}
