/**
 * Clerk User Bridge — Map Clerk user IDs to Prisma User records
 *
 * During the Strangler Fig migration, Clerk users need to be mapped
 * to existing Prisma User records. This bridge handles that lookup:
 *
 *   1. Fast path: look up by clerkId column (after migration runs)
 *   2. Fallback:  fetch user email from Clerk, look up by email, auto-link
 *
 * Apply the SQL migration before relying on the fast path:
 *   prisma/migrations/20260225000000_add_clerk_id/migration.sql
 */

import { convexQuery } from "./convex-client";
import { logger } from "./logger";
export interface BridgedUser {
  id: string;
  email: string;
  name: string;
  role: string;
  organizationId?: string | null;
  orgRole?: string | null;
  allowedTabs?: string[] | null;
  stripeCustomerId?: string | null;
}

/** Map an organization_members role to its allowed tab list. */
async function resolveAllowedTabs(role: string): Promise<string[]> {
  const { STAFF_ALLOWED_TABS, ADMIN_ALLOWED_TABS, OWNER_ALLOWED_TABS } =
    await import("@/lib/types/organization.types");
  switch (role) {
    case "OWNER":
      return OWNER_ALLOWED_TABS;
    case "ADMIN":
      return ADMIN_ALLOWED_TABS;
    case "STAFF":
      return STAFF_ALLOWED_TABS;
    default:
      return [];
  }
}

/**
 * Given a Clerk userId, find the corresponding Prisma User record.
 *
 * Strategy:
 *   1. Try clerkId column (fast — O(1) indexed lookup)
 *   2. Fetch email from Clerk currentUser(), look up by email
 *   3. Auto-link clerkId to the matched record for future fast-path lookups
 *
 * Returns null if no match or Clerk is misconfigured.
 */
/** Shared select shape for user lookups — includes org membership in one query. */
const USER_SELECT = {
  id: true,
  email: true,
  name: true,
  role: true,
  stripeCustomerId: true,
  organization_members: {
    select: { organizationId: true, role: true },
    take: 1,
  },
} as const;

/** Enrich a raw Prisma user row into a fully-populated BridgedUser. */
async function enrichUser(raw: {
  id: string;
  email: string;
  name: string;
  role: string;
  stripeCustomerId: string | null;
  organization_members: { organizationId: string; role: string }[];
}): Promise<BridgedUser> {
  // Prefer prismaId for the user ID since downstream code (checkDashboardAccess)
  // queries Prisma with this ID. Fall back to appUserId/_id for Convex-only users.
  const userId = raw.prismaId || raw.appUserId || raw._id;
  // For Convex org queries, use the Convex-native ID
  const convexUserId = getStableAppUserId(raw);

  const orgState = await convexQuery<{
    organization: { id: string };
    userRole: string;
  }>("organizations/queries:getOrganizationStateByUserId", {
    userId: convexUserId,
  });

  return {
    id: raw.id,
    email: raw.email,
    name: raw.name,
    role: raw.role,
    stripeCustomerId: raw.stripeCustomerId,
    organizationId: membership?.organizationId ?? null,
    orgRole: membership?.role ?? null,
    allowedTabs,
  };
}

export async function bridgeClerkUser(
  clerkUserId: string,
): Promise<BridgedUser | null> {
  try {
    // Convex fast path
    try {
      const { convexQuery } = await import("@/lib/convex-client");

      const convexUser = await convexQuery<{
        _id: string;
        email: string;
        name: string;
        role: string;
        stripeCustomerId?: string | null;
        prismaId?: string | null;
      }>("auth/queries:getUserByClerkId", { clerkId: clerkUserId });

      if (convexUser) {
        return {
          id: convexUser.prismaId ?? convexUser._id,
          email: convexUser.email,
          name: convexUser.name,
          role: convexUser.role,
          stripeCustomerId: convexUser.stripeCustomerId ?? null,
          organizationId: null,
          orgRole: null,
          allowedTabs: [],
        };
      }
      // Not in Convex yet → fall through to Prisma lookup
    } catch (err) {
      logger.warn(
        "Convex lookup failed in bridgeClerkUser, falling back to Prisma",
        {
          clerkUserId,
          err,
        },
      );
    }

    // Fast path: indexed clerkId lookup
    const byClerkId = await prisma.users.findFirst({
      where: { clerkId: clerkUserId },
      select: USER_SELECT,
    });
    if (byClerkId) return enrichUser(byClerkId);

    // Fallback: resolve email from Clerk then look up in Prisma
    const { currentUser } = await import("@clerk/nextjs/server");
    const clerkUser = await currentUser();

    if (!clerkUser || clerkUser.id !== clerkUserId) {
      logger.warn("Clerk user mismatch during bridge", {
        expected: clerkUserId,
        got: clerkUser?.id,
      });
      return null;
    }

    const primaryEmail = clerkUser.emailAddresses.find(
      (e) => e.id === clerkUser.primaryEmailAddressId,
    )?.emailAddress;

    if (!primaryEmail) {
      logger.warn("Clerk user has no primary email", { clerkUserId });
      return null;
    }

    const byEmail = await prisma.users.findUnique({
      where: { email: primaryEmail },
      select: USER_SELECT,
    });

    if (!byEmail) {
      // Bug 3 fix: new Clerk user with no Prisma record — create one on the fly
      logger.info("Creating new Prisma user for Clerk user", {
        clerkUserId,
        email: primaryEmail,
      });
      const displayName =
        clerkUser.fullName ??
        [clerkUser.firstName, clerkUser.lastName].filter(Boolean).join(" ") ??
        "";
      const created = await prisma.users.create({
        data: {
          id: createId(),
          email: primaryEmail,
          name: displayName,
          clerkId: clerkUserId,
          role: "CUSTOMER",
          updatedAt: new Date(),
        },
        select: USER_SELECT,
      });
      return enrichUser(created);
    }

    // Auto-link for future fast-path lookups (fire-and-forget)
    linkClerkToExistingUser(clerkUserId, primaryEmail).catch((err) =>
      logger.error("Failed to auto-link clerkId", { clerkUserId, err }),
    );

    return enrichUser(byEmail);
  } catch (error) {
    logger.error("bridgeClerkUser failed", { clerkUserId, error });
    return null;
  }
}

/**
 * Given a Clerk userId and email, find the Prisma user and link their clerkId.
 *
 * Safe to call repeatedly — skips update if already linked.
 * Returns the Prisma user, or null if email not found.
 */
export async function linkClerkToExistingUser(
  clerkUserId: string,
  email: string,
): Promise<BridgedUser | null> {
  try {
    const user = await prisma.users.findUnique({
      where: { email },
      select: { id: true, clerkId: true },
    });

    if (!user) {
      logger.warn("linkClerkToExistingUser: no user found for email", {
        email,
        clerkUserId,
      });
      return null;
    }

    if (!user.clerkId) {
      await prisma.users.update({
        where: { id: user.id },
        data: { clerkId: clerkUserId, updatedAt: new Date() },
      });
      logger.info("Linked Clerk user to existing Prisma user", {
        userId: user.id,
        clerkUserId,
      });
    }

    // Re-fetch with full select so org data is included
    const linked = await prisma.users.findUnique({
      where: { id: user.id },
      select: USER_SELECT,
    });
    if (!linked) return null;
    return enrichUser(linked);
  } catch (error) {
    logger.error("linkClerkToExistingUser failed", {
      clerkUserId,
      email,
      error,
    });
    return null;
  }
}
