/**
 * Brand Access Control
 * ────────────────────
 * Authoritative gate for which brands a user can read/write. Two-layer
 * check:
 *
 *   1. OrgMembership.role === "owner"  → unconditional access to every
 *                                         brand in the org.
 *   2. BrandMembership                  → explicit per-brand grant.
 *
 * Every /api/* route that takes a brandId must call verifyBrandAccess
 * before reading the brand's data. The sidebar's brand switcher uses
 * listAccessibleBrands() to filter the dropdown so members never see
 * brands they don't own.
 */

import { prisma } from "@/lib/prisma";
import { trackEvent } from "@/lib/services/platform-telemetry";

/** Does userId have access to brandId?
 *
 *  Returns:
 *    true  → allowed (owner of the org OR explicit BrandMembership)
 *    false → denied; caller should 403.
 *
 *  When `request` is passed (for IP / UA capture) and access is
 *  denied, fires a fire-and-forget PlatformEvent under the security
 *  category so the admin Activity feed surfaces probing attempts.
 *  Telemetry is non-blocking — a denied access never waits on an
 *  audit-log insert.
 */
export async function verifyBrandAccess(
  userId: string,
  brandId: string,
  request?: Request,
): Promise<boolean> {
  if (!userId || !brandId) return false;

  const brand = await prisma.brand.findUnique({
    where: { id: brandId },
    select: {
      orgId: true,
      memberships: { where: { userId }, select: { id: true }, take: 1 },
      organization: {
        select: {
          memberships: { where: { userId }, select: { role: true }, take: 1 },
        },
      },
    },
  });
  // Brand id doesn't exist. Could be an honest 404 (a deleted brand
  // someone bookmarked) but is also indistinguishable from id-
  // enumeration — log either way.
  if (!brand) {
    logUnauthorized(userId, brandId, "brand_not_found", request);
    return false;
  }

  // Org owner → unconditional access to all brands in the org.
  const orgRole = brand.organization.memberships[0]?.role;
  if (orgRole === "owner") return true;

  // Explicit brand grant.
  if (brand.memberships.length > 0) return true;

  // Org admin → by policy admins see every brand in their org.
  // Adjust if you want admins to require explicit grants too; the
  // Team tab only wires per-member access for non-owner non-admin
  // seats, so downgrading admin here would silently lock them out
  // of their own brands.
  if (orgRole === "admin") return true;

  // Genuine deny — user exists, brand exists, but the user has
  // no org-role + no explicit grant. Most likely cause: a member
  // who tweaked the brandId in the URL or someone enumerating
  // brand ids.
  logUnauthorized(
    userId,
    brandId,
    orgRole ? "no_brand_grant" : "outside_org",
    request,
  );
  return false;
}

/** Fire-and-forget audit event. Caught + swallowed so a logging
 *  failure never bubbles into a user-facing request. */
function logUnauthorized(
  userId: string,
  attemptedBrandId: string,
  reason: string,
  request: Request | undefined,
): void {
  try {
    trackEvent({
      userId,
      brandId: null,
      action: "unauthorized_brand_access_attempt",
      // "admin" is the closest fit in PlatformEventCategory for
      // security/audit events; the action key carries the real
      // semantics so admin filters can pick it out by action.
      category: "admin",
      metadata: { attemptedBrandId, reason },
      request: request ?? null,
    });
  } catch { /* never throw out of an access check */ }
}

/** Return the full list of brands userId can access in orgId. Used
 *  by the brand-switcher fetch + the Brands Hub listing. */
export async function listAccessibleBrands(userId: string, orgId: string) {
  if (!userId || !orgId) {
    console.warn("[brand-access] listAccessibleBrands called with empty userId or orgId", { userId, orgId });
    return [];
  }

  const membership = await prisma.orgMembership.findFirst({
    where: { userId, orgId },
    select: { role: true },
  });

  if (!membership) {
    console.warn("[brand-access] No OrgMembership found", { userId, orgId });

    // Fallback 1: check if user has membership in a DIFFERENT org
    const anyMembership = await prisma.orgMembership.findFirst({
      where: { userId },
      select: { role: true, orgId: true },
    });
    if (anyMembership) {
      console.warn("[brand-access] User has membership in different org — using that one", { userId, memberOrgId: anyMembership.orgId, requestedOrgId: orgId });
      if (anyMembership.role === "owner" || anyMembership.role === "admin") {
        return prisma.brand.findMany({
          where: { orgId: anyMembership.orgId, status: { not: "deleted" } },
          orderBy: { createdAt: "asc" },
        });
      }
    }

    // Fallback 2: check by email — catches ID mismatches between
    // Clerk userId and DB User.id
    const user = await prisma.user.findUnique({
      where: { id: userId },
      select: { email: true },
    });
    if (user?.email) {
      const byEmail = await prisma.user.findFirst({
        where: { email: user.email },
        include: { memberships: { where: { orgId }, select: { role: true } } },
      });
      if (byEmail && byEmail.memberships.length > 0) {
        const role = byEmail.memberships[0].role;
        console.warn("[brand-access] Found membership via email fallback", { email: user.email, role });
        if (role === "owner" || role === "admin") {
          return prisma.brand.findMany({
            where: { orgId, status: { not: "deleted" } },
            orderBy: { createdAt: "asc" },
          });
        }
      }
    }

    return [];
  }

  if (membership.role === "owner" || membership.role === "admin") {
    return prisma.brand.findMany({
      where: { orgId, status: { not: "deleted" } },
      orderBy: { createdAt: "asc" },
    });
  }

  // Non-owner members see only brands with an explicit membership
  // row. Join BrandMembership → Brand + restrict to this org so a
  // membership rigged for a brand outside the user's org can't leak
  // in (shouldn't happen but belt-and-braces).
  const grants = await prisma.brandMembership.findMany({
    where: { userId, brand: { orgId } },
    include: { brand: true },
    orderBy: { brand: { createdAt: "asc" } },
  });
  return grants.map((g) => g.brand);
}

/** List BrandMemberships for every user in an org — powers the Team
 *  tab's "Brand Access" column. Returned map is userId → brandId[]. */
export async function brandAccessByUserInOrg(orgId: string): Promise<Map<string, string[]>> {
  const rows = await prisma.brandMembership.findMany({
    where: { brand: { orgId } },
    select: { userId: true, brandId: true },
  });
  const map = new Map<string, string[]>();
  for (const row of rows) {
    const list = map.get(row.userId) ?? [];
    list.push(row.brandId);
    map.set(row.userId, list);
  }
  return map;
}
