import { ConvexError } from "convex/values";

import type { Id } from "./_generated/dataModel";
import type { MutationCtx, QueryCtx } from "./_generated/server";

export interface AuthContext {
  readonly auth: {
    readonly getUserIdentity: () => Promise<{
      readonly tokenIdentifier: string;
    } | null>;
  };
}

export const requireAuthUserId = async (ctx: AuthContext): Promise<string> => {
  const identity = await ctx.auth.getUserIdentity();
  if (!identity) {
    throw new ConvexError("Authentication required");
  }
  return identity.tokenIdentifier;
};

/**
 * Resolve the authenticated identity and prove it is a member of the given
 * organization. Throws on unauthenticated or non-member access. Returns the
 * canonical user ID (the Better Auth `tokenIdentifier`).
 */
export const requireOrganizationMember = async (
  ctx: QueryCtx | MutationCtx,
  organizationId: Id<"organizations">
): Promise<string> => {
  const userId = await requireAuthUserId(ctx);
  const membership = await ctx.db
    .query("organizationMembers")
    .withIndex("by_organizationId_and_userId", (q) =>
      q.eq("organizationId", organizationId).eq("userId", userId)
    )
    .unique();
  if (!membership) {
    throw new ConvexError("Organization membership required");
  }
  return userId;
};

/**
 * Resolve the authenticated user's personal/current organization. Browsers
 * never submit organization IDs for Project commands; this resolves the
 * tenant boundary server-side through the authenticated identity.
 */
export const requireCurrentOrganization = async (
  ctx: QueryCtx | MutationCtx
): Promise<{ organizationId: Id<"organizations">; userId: string }> => {
  const userId = await requireAuthUserId(ctx);
  const organization = await ctx.db
    .query("organizations")
    .withIndex("by_createdBy_and_kind", (q) =>
      q.eq("createdBy", userId).eq("kind", "personal")
    )
    .unique();
  if (!organization) {
    throw new ConvexError("Organization not found");
  }
  return { organizationId: organization._id, userId };
};

/**
 * Prove the authenticated user is a member of the project's organization.
 * Projects are organization-scoped; unauthorized single-object operations
 * throw "Project not found" to avoid leaking existence.
 */
export const requireProjectMember = async (
  ctx: QueryCtx | MutationCtx,
  projectId: Id<"projects">
): Promise<{ organizationId: Id<"organizations">; userId: string }> => {
  const project = await ctx.db.get(projectId);
  if (!project) {
    throw new ConvexError("Project not found");
  }
  const userId = await requireOrganizationMember(ctx, project.organizationId);
  return { organizationId: project.organizationId, userId };
};
