import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit'
import { db } from '@sim/db'
import { account, credentialSet, credentialSetMember, member, user } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { generateId } from '@sim/utils/id'
import { and, eq, inArray } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { removeCredentialSetMemberQuerySchema } from '@/lib/api/contracts/credential-sets'
import { getValidationErrorMessage } from '@/lib/api/server'
import { getSession } from '@/lib/auth'
import { hasCredentialSetsAccess } from '@/lib/billing'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server'

const logger = createLogger('CredentialSetMembers')

async function getCredentialSetWithAccess(credentialSetId: string, userId: string) {
  const [set] = await db
    .select({
      id: credentialSet.id,
      name: credentialSet.name,
      organizationId: credentialSet.organizationId,
      providerId: credentialSet.providerId,
    })
    .from(credentialSet)
    .where(eq(credentialSet.id, credentialSetId))
    .limit(1)

  if (!set) return null

  const [membership] = await db
    .select({ role: member.role })
    .from(member)
    .where(and(eq(member.userId, userId), eq(member.organizationId, set.organizationId)))
    .limit(1)

  if (!membership) return null

  return { set, role: membership.role }
}

export const GET = withRouteHandler(
  async (req: NextRequest, { params }: { params: Promise<{ id: string }> }) => {
    const session = await getSession()

    if (!session?.user?.id) {
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
    }

    // Check plan access (team/enterprise) or env var override
    const hasAccess = await hasCredentialSetsAccess(session.user.id)
    if (!hasAccess) {
      return NextResponse.json(
        { error: 'Credential sets require a Team or Enterprise plan' },
        { status: 403 }
      )
    }

    const { id } = await params
    const result = await getCredentialSetWithAccess(id, session.user.id)

    if (!result) {
      return NextResponse.json({ error: 'Credential set not found' }, { status: 404 })
    }

    const members = await db
      .select({
        id: credentialSetMember.id,
        userId: credentialSetMember.userId,
        status: credentialSetMember.status,
        joinedAt: credentialSetMember.joinedAt,
        createdAt: credentialSetMember.createdAt,
        userName: user.name,
        userEmail: user.email,
        userImage: user.image,
      })
      .from(credentialSetMember)
      .leftJoin(user, eq(credentialSetMember.userId, user.id))
      .where(eq(credentialSetMember.credentialSetId, id))

    // Get credentials for all active members filtered by the polling group's provider
    const activeMembers = members.filter((m) => m.status === 'active')
    const memberUserIds = activeMembers.map((m) => m.userId)

    let credentials: { userId: string; providerId: string; accountId: string }[] = []
    if (memberUserIds.length > 0 && result.set.providerId) {
      credentials = await db
        .select({
          userId: account.userId,
          providerId: account.providerId,
          accountId: account.accountId,
        })
        .from(account)
        .where(
          and(inArray(account.userId, memberUserIds), eq(account.providerId, result.set.providerId))
        )
    }

    // Group credentials by userId
    const credentialsByUser = credentials.reduce(
      (acc, cred) => {
        if (!acc[cred.userId]) {
          acc[cred.userId] = []
        }
        acc[cred.userId].push({
          providerId: cred.providerId,
          accountId: cred.accountId,
        })
        return acc
      },
      {} as Record<string, { providerId: string; accountId: string }[]>
    )

    // Attach credentials to members
    const membersWithCredentials = members.map((m) => ({
      ...m,
      credentials: credentialsByUser[m.userId] || [],
    }))

    return NextResponse.json({ members: membersWithCredentials })
  }
)

export const DELETE = withRouteHandler(
  async (req: NextRequest, { params }: { params: Promise<{ id: string }> }) => {
    const session = await getSession()

    if (!session?.user?.id) {
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
    }

    // Check plan access (team/enterprise) or env var override
    const hasAccess = await hasCredentialSetsAccess(session.user.id)
    if (!hasAccess) {
      return NextResponse.json(
        { error: 'Credential sets require a Team or Enterprise plan' },
        { status: 403 }
      )
    }

    const { id } = await params
    const { searchParams } = new URL(req.url)
    const validation = removeCredentialSetMemberQuerySchema.safeParse({
      memberId: searchParams.get('memberId') ?? '',
    })

    if (!validation.success) {
      return NextResponse.json(
        { error: getValidationErrorMessage(validation.error) },
        { status: 400 }
      )
    }

    const { memberId } = validation.data

    try {
      const result = await getCredentialSetWithAccess(id, session.user.id)

      if (!result) {
        return NextResponse.json({ error: 'Credential set not found' }, { status: 404 })
      }

      if (result.role !== 'admin' && result.role !== 'owner') {
        return NextResponse.json({ error: 'Admin or owner permissions required' }, { status: 403 })
      }

      const [memberToRemove] = await db
        .select({
          id: credentialSetMember.id,
          credentialSetId: credentialSetMember.credentialSetId,
          userId: credentialSetMember.userId,
          status: credentialSetMember.status,
          email: user.email,
        })
        .from(credentialSetMember)
        .innerJoin(user, eq(credentialSetMember.userId, user.id))
        .where(
          and(eq(credentialSetMember.id, memberId), eq(credentialSetMember.credentialSetId, id))
        )
        .limit(1)

      if (!memberToRemove) {
        return NextResponse.json({ error: 'Member not found' }, { status: 404 })
      }

      const requestId = generateId().slice(0, 8)

      await db.delete(credentialSetMember).where(eq(credentialSetMember.id, memberId))

      // Runs after the deletion commits: the sync performs external HTTP
      // (OAuth refresh, provider unsubscribe) and must not hold a pooled
      // connection. A sync failure must not fail the committed mutation —
      // it self-heals on the next membership change/deploy.
      try {
        const syncResult = await syncAllWebhooksForCredentialSet(id, requestId)
        logger.info('Synced webhooks after member removed', {
          credentialSetId: id,
          ...syncResult,
        })
      } catch (syncError) {
        logger.error('Webhook sync failed after member removal', {
          credentialSetId: id,
          error: syncError,
        })
      }

      logger.info('Removed member from credential set', {
        credentialSetId: id,
        memberId,
        userId: session.user.id,
      })

      recordAudit({
        workspaceId: null,
        actorId: session.user.id,
        action: AuditAction.CREDENTIAL_SET_MEMBER_REMOVED,
        resourceType: AuditResourceType.CREDENTIAL_SET,
        resourceId: id,
        actorName: session.user.name ?? undefined,
        actorEmail: session.user.email ?? undefined,
        resourceName: result.set.name,
        description: `Removed member from credential set "${result.set.name}"`,
        metadata: {
          memberId,
          memberUserId: memberToRemove.userId,
          targetEmail: memberToRemove.email ?? undefined,
          providerId: result.set.providerId,
        },
        request: req,
      })

      return NextResponse.json({ success: true })
    } catch (error) {
      logger.error('Error removing member from credential set', error)
      return NextResponse.json({ error: 'Failed to remove member' }, { status: 500 })
    }
  }
)
