import { db } from '@sim/db'
import { account, credential, credentialMember } from '@sim/db/schema'
import { and, eq, inArray, notInArray } from 'drizzle-orm'
import { generateId } from '@/lib/core/utils/uuid'
import { getServiceConfigByProviderId } from '@/lib/oauth'

/** Provider IDs that are not real OAuth integrations (login-only social providers and password) */
const NON_OAUTH_PROVIDER_IDS = ['credential', 'google', 'github'] as const

interface SyncWorkspaceOAuthCredentialsForUserParams {
  workspaceId: string
  userId: string
}

interface SyncWorkspaceOAuthCredentialsForUserResult {
  updatedMemberships: number
}

function getPostgresErrorCode(error: unknown): string | undefined {
  if (!error || typeof error !== 'object') return undefined
  const err = error as { code?: string; cause?: { code?: string } }
  return err.code || err.cause?.code
}

/**
 * Normalizes display names and ensures credential memberships for existing
 * workspace-scoped OAuth credentials. Does not create new credentials —
 * credential creation is handled by the draft-based OAuth connect flow.
 */
export async function syncWorkspaceOAuthCredentialsForUser(
  params: SyncWorkspaceOAuthCredentialsForUserParams
): Promise<SyncWorkspaceOAuthCredentialsForUserResult> {
  const { workspaceId, userId } = params

  const userAccounts = await db
    .select({
      id: account.id,
      providerId: account.providerId,
      accountId: account.accountId,
    })
    .from(account)
    .where(
      and(eq(account.userId, userId), notInArray(account.providerId, [...NON_OAUTH_PROVIDER_IDS]))
    )

  if (userAccounts.length === 0) {
    return { updatedMemberships: 0 }
  }

  const accountIds = userAccounts.map((row) => row.id)
  const existingCredentials = await db
    .select({
      id: credential.id,
      displayName: credential.displayName,
      providerId: credential.providerId,
      accountId: credential.accountId,
    })
    .from(credential)
    .where(
      and(
        eq(credential.workspaceId, workspaceId),
        eq(credential.type, 'oauth'),
        inArray(credential.accountId, accountIds)
      )
    )

  const now = new Date()
  const userAccountById = new Map(userAccounts.map((row) => [row.id, row]))
  for (const existingCredential of existingCredentials) {
    if (!existingCredential.accountId) continue
    const linkedAccount = userAccountById.get(existingCredential.accountId)
    if (!linkedAccount) continue

    const normalizedLabel =
      getServiceConfigByProviderId(linkedAccount.providerId)?.name || linkedAccount.providerId
    const shouldNormalizeDisplayName =
      existingCredential.displayName === linkedAccount.accountId ||
      existingCredential.displayName === linkedAccount.providerId

    if (!shouldNormalizeDisplayName || existingCredential.displayName === normalizedLabel) {
      continue
    }

    await db
      .update(credential)
      .set({
        displayName: normalizedLabel,
        updatedAt: now,
      })
      .where(eq(credential.id, existingCredential.id))
  }

  const credentialRows = await db
    .select({ id: credential.id, accountId: credential.accountId })
    .from(credential)
    .where(
      and(
        eq(credential.workspaceId, workspaceId),
        eq(credential.type, 'oauth'),
        inArray(credential.accountId, accountIds)
      )
    )

  const credentialIdByAccountId = new Map(
    credentialRows.filter((row) => Boolean(row.accountId)).map((row) => [row.accountId!, row.id])
  )
  const allCredentialIds = Array.from(credentialIdByAccountId.values())
  if (allCredentialIds.length === 0) {
    return { updatedMemberships: 0 }
  }

  const existingMemberships = await db
    .select({
      id: credentialMember.id,
      credentialId: credentialMember.credentialId,
      joinedAt: credentialMember.joinedAt,
    })
    .from(credentialMember)
    .where(
      and(
        inArray(credentialMember.credentialId, allCredentialIds),
        eq(credentialMember.userId, userId)
      )
    )

  const membershipByCredentialId = new Map(
    existingMemberships.map((row) => [row.credentialId, row])
  )
  let updatedMemberships = 0

  for (const credentialId of allCredentialIds) {
    const existingMembership = membershipByCredentialId.get(credentialId)
    if (existingMembership) {
      await db
        .update(credentialMember)
        .set({
          role: 'admin',
          status: 'active',
          joinedAt: existingMembership.joinedAt ?? now,
          invitedBy: userId,
          updatedAt: now,
        })
        .where(eq(credentialMember.id, existingMembership.id))
      updatedMemberships += 1
      continue
    }

    try {
      await db.insert(credentialMember).values({
        id: generateId(),
        credentialId,
        userId,
        role: 'admin',
        status: 'active',
        joinedAt: now,
        invitedBy: userId,
        createdAt: now,
        updatedAt: now,
      })
      updatedMemberships += 1
    } catch (error) {
      if (getPostgresErrorCode(error) !== '23505') {
        throw error
      }
    }
  }

  return { updatedMemberships }
}
